Port 80 stammt aus dem Kiosk-Setup auf dem Pi, wo Chromium die Seite ohne Portangabe unter http://localhost oeffnet. Als Default erzwingt er lokal erhoehte Rechte. Auf dem Pi stattdessen PORT=80 setzen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| scoreboard | ||
| .gitignore | ||
| README.md | ||
Scoreboard-JS
Echtzeit-Anzeigetafel für Sportveranstaltungen — zeigt Timer und Spielstand auf einem separaten Monitor an und wird über ein Admin-Panel gesteuert.
Übersicht
Das System besteht aus zwei Teilen:
| Teil | URL | Beschreibung |
|---|---|---|
| Monitor | http://<host>:3000/ |
Vollbildanzeige für den Beamer/Monitor (Timer + Score) |
| Admin-Panel | http://<host>:3000/admin |
Steuerung von Timer und Scoreboard |
Alle Änderungen vom Admin-Panel werden über WebSockets (Socket.IO) in Echtzeit auf dem Monitor angezeigt — ohne Seiten-Reload.
Voraussetzungen
- Node.js (v18 oder neuer empfohlen)
- npm
Installation & Start
cd scoreboard
npm install
npm start
Der Server startet mit nodemon und ist anschließend unter Port 3000 erreichbar.
Der Socket.IO-Server läuft separat auf Port 3001.
Projektstruktur
scoreboard/
├── app.js # Express-App, Middleware, Routen
├── bin/www # HTTP-Server, startet die App
├── controllers/
│ ├── socketio.js # Socket.IO-Server (Port 3001)
│ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec)
│ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel)
│ └── cli.js # CLI-Controller (aktuell Testcode)
├── routes/
│ ├── admin.js # REST-Endpunkte für das Admin-Panel
│ └── index.js # Monitor-Ansicht + WebSocket-Verbindungshandler
├── views/
│ ├── admin.hbs # Admin-Panel (Handlebars-Template)
│ ├── index.hbs # Monitor ohne Scoreboard (nur Timer)
│ └── indexScore.hbs # Monitor mit Scoreboard (Timer + Score + Teamnamen)
└── public/
├── javascripts/
│ ├── admin.js # Frontend-Logik Admin-Panel
│ ├── index.js # Frontend-Logik Monitor (ohne Score)
│ └── indexScore.js # Frontend-Logik Monitor (mit Score)
└── stylesheets/
├── admin.css
├── index.css
├── indexScore.css
└── seven-segment.css # Seven-Segment-Display-Schrift für den Timer
Funktionen
Timer
- Countdown-Timer mit konfigurierbarer Startzeit (Standard: 7:00 Minuten)
- Start / Pause
- Zurücksetzen auf eine neue Zeit (0:00 bis 10:00, in 15-Sekunden-Schritten)
- Feineinstellung im laufenden Betrieb: ±1s, ±5s, ±10s
- Nach Ablauf wird
timerEndedan alle Clients gesendet und "ENDE" auf dem Monitor angezeigt
Scoreboard
- Punktestand für Team A und Team B
- Punkte einzeln erhöhen (+1) oder verringern (-1)
- Score direkt auf einen bestimmten Wert setzen
- Score zurücksetzen (beide Teams auf 0)
- Scoreboard ein-/ausblenden: Der Monitor wechselt zwischen Timer-only-Ansicht (
index.hbs) und Timer+Score-Ansicht (indexScore.hbs)
Teams konfigurieren
Jedes Team hat folgende Felder:
| Feld | Beschreibung |
|---|---|
name |
Hauptname des Teams |
name2 |
Zweiter Name (nur bei Spielgemeinschaft) |
isSpielgemeinschaft |
Wenn aktiv, werden beide Namen untereinander angezeigt |
Seitenwechsel (Halbzeit)
Über "Seitenwechsel" werden Team A und Team B auf dem Monitor die Seiten tauschen — der Spielstand bleibt unverändert, nur die Anzeigepositionen (links/rechts) wechseln.
Monitor neu laden
Der Button "Monitor neu laden" im Admin-Panel sendet ein refresh-Event an alle verbundenen Clients und löst einen Seiten-Reload des Monitors aus. Nützlich z.B. nach dem Ein-/Ausblenden des Scoreboards.
Architektur / Datenfluss
Admin-Panel (Browser)
│
│ HTTP REST (fetch)
▼
Express-Server :3000
routes/admin.js
│
├── controllers/timer.js ──┐
└── controllers/score.js ──┤
│ io.sockets.emit(event, data)
▼
Socket.IO-Server :3001
│
WebSocket-Verbindung
│
┌───────────────┴───────────────┐
▼ ▼
Monitor (index.hbs) Monitor (indexScore.hbs)
index.js indexScore.js
WebSocket-Events
| Event | Richtung | Beschreibung |
|---|---|---|
timerDurationLeft |
Server → Client | Aktuelle Restzeit als String MM:SS |
timerEnded |
Server → Client | Timer abgelaufen |
score |
Server → Client | Aktueller Spielstand als String X:Y |
scoreSideswitch |
Server → Client | Seitenwechsel ausgelöst — Frontend neu laden |
refresh |
Server → Client | Kompletten Seiten-Reload auslösen |
REST-API (Admin-Endpunkte)
Alle Endpunkte sind unter /admin erreichbar.
Allgemein
| Methode | Endpunkt | Beschreibung |
|---|---|---|
| GET | /admin/refreshMonitor |
Sendet refresh an alle Monitors |
Timer
| Methode | Endpunkt | Body | Beschreibung |
|---|---|---|---|
| GET | /admin/timerStart |
— | Timer starten |
| GET | /admin/timerPause |
— | Timer pausieren |
| POST | /admin/timerReset |
{ duration: <Sekunden> } |
Timer zurücksetzen |
| POST | /admin/timerIncDec |
{ value: <Sekunden> } |
Timer erhöhen (positiv) / verringern (negativ) |
| GET | /admin/timerGetValues |
— | Aktuelle Timer-Werte abrufen |
Antwortformat timerGetValues:
{
"isPaused": true,
"duration": "<ISO 8601>",
"durationLeft": "<ISO 8601>",
"print": "07:00"
}
Scoreboard
| Methode | Endpunkt | Body | Beschreibung |
|---|---|---|---|
| GET | /admin/scoreToggle |
— | Scoreboard ein-/ausblenden |
| GET | /admin/scoreGetValues |
— | Aktuelle Score-Werte abrufen |
| GET | /admin/scoreClearScore |
— | Spielstand beider Teams auf 0 |
| GET | /admin/scoreToggleSideswitch |
— | Seiten der Teams tauschen |
| POST | /admin/scoreSetScore |
{ team, score } |
Score direkt setzen |
| POST | /admin/scoreAlterScore |
{ team, dir } |
Score um 1 ändern (dir: "inc" / "dec") |
| POST | /admin/scoreConfigTeams |
{ teamA: { name, name2, isSpielgemeinschaft }, teamB: { ... } } |
Teams konfigurieren |
team ist jeweils "teamA" oder "teamB".
Antwortformat scoreGetValues:
{
"enabled": false,
"teamA": { "name": "Team A", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
"teamB": { "name": "Team B", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
"sideswitch": false,
"print": "0:0"
}
Abhängigkeiten
| Paket | Verwendung |
|---|---|
express |
HTTP-Server und Routing |
socket.io |
WebSocket-Kommunikation |
moment |
Timer-Berechnung und Zeitformatierung |
hbs |
Handlebars als View-Engine |
morgan |
HTTP-Request-Logging |
nodemon |
Automatischer Server-Neustart bei Dateiänderungen |