Initial commit

This commit is contained in:
2026-07-16 03:37:10 +02:00
commit 40755ff5ef
4 changed files with 219 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.pio/
.vscode/
*.swp
+35
View File
@@ -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.
+9
View File
@@ -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
View File
@@ -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();
}
}