Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.pio/
|
||||
.vscode/
|
||||
*.swp
|
||||
@@ -0,0 +1,35 @@
|
||||
# Baseball Display
|
||||
|
||||
A starter ESP32 firmware project for a baseball scoreboard with a seven-segment-style LED display built from WS2811/SK6812-compatible LEDs.
|
||||
|
||||
## Features
|
||||
- Displays home score, away score, inning, outs, balls, strikes, current time, and elapsed game time
|
||||
- Serves a small web control page from the ESP32 for remote updates
|
||||
- Uses a configurable LED strip / pixel array as the display backend
|
||||
|
||||
## Hardware
|
||||
- ESP32 development board
|
||||
- WS2811/SK6812-compatible LED strip or pixels
|
||||
- Data pin: GPIO 13
|
||||
- Power: 5 V, with ground connected and a bulk capacitor near the LEDs
|
||||
|
||||
## Build
|
||||
```bash
|
||||
pio run
|
||||
```
|
||||
|
||||
## Flash
|
||||
```bash
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
## Usage
|
||||
1. Flash the firmware to the ESP32.
|
||||
2. Connect to the Wi-Fi access point named `BaseballDisplay` with password `scoreboard`.
|
||||
3. Open `http://192.168.4.1/` in a browser.
|
||||
4. Use the controls to update the scoreboard state.
|
||||
|
||||
## Next steps
|
||||
- Replace the simple segment layout with your actual physical LED geometry.
|
||||
- Add a real controller device or a more polished UI.
|
||||
- Integrate real time sync or a scoreboard backend.
|
||||
@@ -0,0 +1,9 @@
|
||||
[env:esp32dev]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
build_flags = -DCORE_DEBUG_LEVEL=0
|
||||
lib_deps =
|
||||
fastled/FastLED@^3.9.0
|
||||
bblanchon/ArduinoJson@^7.0.0
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <FastLED.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LED_PIN 13
|
||||
#define LED_TYPE WS2812B
|
||||
#define COLOR_ORDER GRB
|
||||
#define NUM_LEDS 120
|
||||
#define BRIGHTNESS 64
|
||||
|
||||
CRGB leds[NUM_LEDS];
|
||||
WebServer server(80);
|
||||
|
||||
struct ScoreState {
|
||||
int homeScore = 0;
|
||||
int awayScore = 0;
|
||||
int inning = 1;
|
||||
int outs = 0;
|
||||
int balls = 0;
|
||||
int strikes = 0;
|
||||
int hour = 12;
|
||||
int minute = 0;
|
||||
unsigned long gameStartMs = 0;
|
||||
};
|
||||
|
||||
ScoreState state;
|
||||
|
||||
void setPixel(int index, CRGB color) {
|
||||
if (index >= 0 && index < NUM_LEDS) {
|
||||
leds[index] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void renderDigit(int digit, int startIndex, CRGB color) {
|
||||
static const bool segments[10][7] = {
|
||||
{1, 1, 1, 1, 1, 1, 0},
|
||||
{0, 1, 1, 0, 0, 0, 0},
|
||||
{1, 1, 0, 1, 1, 0, 1},
|
||||
{1, 1, 1, 1, 0, 0, 1},
|
||||
{0, 1, 1, 0, 0, 1, 1},
|
||||
{1, 0, 1, 1, 0, 1, 1},
|
||||
{1, 0, 1, 1, 1, 1, 1},
|
||||
{1, 1, 1, 0, 0, 0, 0},
|
||||
{1, 1, 1, 1, 1, 1, 1},
|
||||
{1, 1, 1, 1, 0, 1, 1},
|
||||
};
|
||||
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
if (segments[digit][i]) {
|
||||
setPixel(startIndex + i, color);
|
||||
} else {
|
||||
setPixel(startIndex + i, CRGB::Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void renderScoreboard() {
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
|
||||
int home = state.homeScore;
|
||||
int away = state.awayScore;
|
||||
int inning = state.inning;
|
||||
int outs = state.outs;
|
||||
int balls = state.balls;
|
||||
int strikes = state.strikes;
|
||||
|
||||
renderDigit(home / 10, 0, CRGB::Green);
|
||||
renderDigit(home % 10, 7, CRGB::Green);
|
||||
renderDigit(away / 10, 14, CRGB::Red);
|
||||
renderDigit(away % 10, 21, CRGB::Red);
|
||||
renderDigit(inning / 10, 28, CRGB::Blue);
|
||||
renderDigit(inning % 10, 35, CRGB::Blue);
|
||||
renderDigit(outs, 42, CRGB::Orange);
|
||||
renderDigit(balls, 49, CRGB::Yellow);
|
||||
renderDigit(strikes, 56, CRGB::Magenta);
|
||||
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
void handleRoot() {
|
||||
String html = R"html(
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Baseball Display</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #111; color: #eee; padding: 24px; }
|
||||
.card { max-width: 420px; margin: 0 auto; background: #222; padding: 20px; border-radius: 12px; }
|
||||
label { display: block; margin-top: 10px; }
|
||||
input, button { width: 100%; padding: 10px; margin-top: 6px; }
|
||||
button { background: #4f46e5; color: white; border: none; border-radius: 8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Baseball Scoreboard</h1>
|
||||
<form action="/update" method="post">
|
||||
<label>Home score</label>
|
||||
<input name="homeScore" value="0" />
|
||||
<label>Away score</label>
|
||||
<input name="awayScore" value="0" />
|
||||
<label>Inning</label>
|
||||
<input name="inning" value="1" />
|
||||
<label>Outs</label>
|
||||
<input name="outs" value="0" />
|
||||
<label>Balls</label>
|
||||
<input name="balls" value="0" />
|
||||
<label>Strikes</label>
|
||||
<input name="strikes" value="0" />
|
||||
<button type="submit">Update display</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)html";
|
||||
server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
void handleUpdate() {
|
||||
if (server.hasArg("homeScore")) state.homeScore = server.arg("homeScore").toInt();
|
||||
if (server.hasArg("awayScore")) state.awayScore = server.arg("awayScore").toInt();
|
||||
if (server.hasArg("inning")) state.inning = server.arg("inning").toInt();
|
||||
if (server.hasArg("outs")) state.outs = server.arg("outs").toInt();
|
||||
if (server.hasArg("balls")) state.balls = server.arg("balls").toInt();
|
||||
if (server.hasArg("strikes")) state.strikes = server.arg("strikes").toInt();
|
||||
|
||||
renderScoreboard();
|
||||
server.sendHeader("Location", "/");
|
||||
server.send(303);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
|
||||
FastLED.setBrightness(BRIGHTNESS);
|
||||
FastLED.clear();
|
||||
FastLED.show();
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP("BaseballDisplay", "scoreboard");
|
||||
IPAddress ip = WiFi.softAPIP();
|
||||
Serial.print("AP IP: ");
|
||||
Serial.println(ip);
|
||||
|
||||
server.on("/", HTTP_GET, handleRoot);
|
||||
server.on("/update", HTTP_POST, handleUpdate);
|
||||
server.begin();
|
||||
|
||||
state.gameStartMs = millis();
|
||||
renderScoreboard();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
server.handleClient();
|
||||
|
||||
static unsigned long lastTimeUpdate = 0;
|
||||
if (millis() - lastTimeUpdate > 1000) {
|
||||
lastTimeUpdate = millis();
|
||||
state.minute = (state.minute + 1) % 60;
|
||||
if (state.minute == 0) {
|
||||
state.hour = (state.hour + 1) % 24;
|
||||
}
|
||||
renderScoreboard();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user