scoreboard-js/README.md
Julian Appel a1cea9c37d README auf den gemergten Stand gebracht
Ergaenzt: Splashscreen als dritte Monitoransicht und Standard beim Start,
Team-Datenbank, Debugfenster, Spiegelung des Adminpanels, Steuerung des
Kiosk-Browsers, die zugehoerigen etc-, db- und score-Endpunkte sowie der
Hinweis zur PORT-Variable. Korrigiert: Portangabe, Projektstruktur,
Antwortformate und die Abhaengigkeitsliste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 20:39:27 +02:00

311 lines
11 KiB
Markdown

# 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 |
| **Admin-Panel** | `http://<host>:3000/admin` | Steuerung von Timer, Scoreboard und Anzeige |
Alle Änderungen vom Admin-Panel werden über **WebSockets (Socket.IO)** in Echtzeit auf dem Monitor angezeigt — ohne Seiten-Reload.
Der Monitor rendert je nach Serverzustand eine von drei Ansichten:
| Ansicht | Template | Wann |
|---|---|---|
| Splashscreen | `splashscreen.hbs` | Solange der Splashscreen aktiv ist (Standard beim Start) |
| Timer + Score | `indexScore.hbs` | Splashscreen aus, Scoreboard aktiv (Standard) |
| Nur Timer | `index.hbs` | Splashscreen aus, Scoreboard deaktiviert |
---
## Voraussetzungen
- Node.js (v18 oder neuer)
- npm
---
## Installation & Start
```bash
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**.
### Port ändern
Der HTTP-Port lässt sich über die Umgebungsvariable `PORT` setzen:
```bash
PORT=80 npm start
```
Auf dem Anzeige-Rechner (Raspberry Pi) wird Port **80** verwendet, damit der
Kiosk-Browser die Seite ohne Portangabe unter `http://localhost` öffnen kann.
Port 80 erfordert erhöhte Rechte.
---
## Projektstruktur
```
scoreboard/
├── app.js # Express-App, Middleware, Routen
├── bin/www # HTTP-Server, startet die App
├── db.json # Persistente Liste bekannter Teamnamen
├── controllers/
│ ├── socketio.js # Socket.IO-Server (Port 3001)
│ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec)
│ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel)
│ ├── db.js # Lesen/Schreiben der Teamnamen in db.json
│ ├── etc.js # Splashscreen-Status
│ └── cli.js # Start/Stop des Kiosk-Browsers auf dem Anzeigerechner
├── routes/
│ ├── admin.js # REST-Endpunkte für das Admin-Panel
│ └── index.js # Monitor-Ansicht + WebSocket-Verbindungshandler
├── views/
│ ├── admin.hbs # Admin-Panel (Handlebars-Template)
│ ├── splashscreen.hbs # Monitor: Vereinslogo
│ ├── index.hbs # Monitor: nur Timer
│ └── indexScore.hbs # Monitor: Timer + Score + Teamnamen
└── public/
├── img/ # Logos für den Splashscreen
├── javascripts/
│ ├── admin.js # Frontend-Logik Admin-Panel
│ ├── index.js # Frontend-Logik Monitor (ohne Score)
│ ├── indexScore.js # Frontend-Logik Monitor (mit Score)
│ └── splashscreen.js # Frontend-Logik Splashscreen (nur Refresh-Handler)
└── stylesheets/
├── admin.css
├── index.css
├── indexScore.css
├── splashscreen.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 `timerEnded` an alle Clients gesendet und "ENDE" auf dem Monitor angezeigt
### Scoreboard
- Punktestand für **Team A** und **Team B**, direkt im Admin-Panel über +1/-1 steuerbar
- Score direkt auf einen bestimmten Wert setzen
- Score zurücksetzen (beide Teams auf 0)
- **Scoreboard ein-/ausblenden:** Der Monitor wechselt zwischen Timer-only-Ansicht und Timer+Score-Ansicht
### 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 |
Die Namen können frei eingetippt oder per Dropdown aus der **Team-Datenbank** gewählt werden.
### Team-Datenbank
Häufig verwendete Teamnamen werden in `db.json` gespeichert und stehen im
Konfigurationsdialog als Auswahl zur Verfügung. Teams lassen sich im
Debugfenster hinzufügen und löschen.
### Seitenwechsel (Halbzeit)
Über "Seitenwechsel" tauschen Team A und Team B auf dem Monitor die Seiten — der Spielstand bleibt unverändert, nur die Anzeigepositionen (links/rechts) wechseln.
### Spiegelung des Admin-Panels
Sitzt die Bedienung **hinter** dem Monitor, stimmt die Links/Rechts-Zuordnung im
Admin-Panel nicht mehr mit der Sicht auf die Anzeigetafel überein. Der Schalter
"Teamanzeige im Adminpanel spiegeln" im Debugfenster dreht die Darstellung im
Admin-Panel um — inklusive der +1/-1-Buttons, die dann dem jeweils richtigen Team
zugeordnet werden. Die Monitoranzeige bleibt davon unberührt.
### Splashscreen
Standardmäßig zeigt der Monitor beim Start das Vereinslogo. Über den Schalter im
Debugfenster wird zwischen Splashscreen und Spielanzeige umgeschaltet.
### Debugfenster
Sammelt die selten benötigten Schalter: Spiegelung, Scoreboard ein/aus,
Splashscreen ein/aus, Monitor neu laden, Kiosk-Browser starten/stoppen sowie die
Verwaltung der Team-Datenbank.
### Kiosk-Browser steuern
Auf dem Anzeige-Rechner (Raspberry Pi) kann der Chromium-Browser aus dem
Admin-Panel heraus im Kiosk-Modus gestartet und wieder beendet werden. Die
Funktion ist Linux-spezifisch und setzt einen Benutzer `pi` mit Zugriff auf
Display `:0` voraus.
### Monitor neu laden
Der Button "Monitor neu laden" sendet ein `refresh`-Event an alle verbundenen Clients und löst einen Seiten-Reload des Monitors aus. Nötig, wenn zwischen zwei Ansichten gewechselt wurde.
---
## Architektur / Datenfluss
```
Admin-Panel (Browser)
│ HTTP REST (fetch)
Express-Server :3000
routes/admin.js
├── controllers/timer.js ──┐
├── controllers/score.js ──┤
├── controllers/etc.js ──┤
├── controllers/db.js ──┐ │
└── controllers/cli.js │ │ io.sockets.emit(event, data)
│ ▼
db.json Socket.IO-Server :3001
WebSocket-Verbindung
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
Monitor (splashscreen) Monitor (index) Monitor (indexScore)
splashscreen.js index.js indexScore.js
```
Der gesamte Spielzustand (Timer, Score, Teams, Schalter) liegt **im
Arbeitsspeicher** der Controller. Ein Serverneustart setzt ihn zurück; einzig die
Teamnamen in `db.json` bleiben erhalten.
### 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 |
| GET | `/admin/openBrowser` | Startet den Kiosk-Browser auf dem Anzeigerechner |
| GET | `/admin/killBrowser` | Beendet den Kiosk-Browser |
### Anzeige (etc)
| Methode | Endpunkt | Beschreibung |
|---|---|---|
| GET | `/admin/etcGetValues` | Aktuellen Splashscreen-Status abrufen |
| GET | `/admin/etcToggleSplashscreen` | Splashscreen ein-/ausblenden |
Antwortformat:
```json
{ "splashscreenEnabled": true }
```
### 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 |
`timerStart` und `timerPause` antworten mit **406**, wenn die Aktion nicht möglich
ist (Timer läuft bereits, ist bereits pausiert oder abgelaufen).
Antwortformat `timerGetValues`:
```json
{
"isPaused": true,
"duration": "PT7M",
"durationLeft": "PT7M",
"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 |
| GET | `/admin/scoreToggleReferenceMirrored` | — | Admin-Panel-Darstellung spiegeln |
| 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`:
```json
{
"enabled": true,
"teamA": { "name": "Team A", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
"teamB": { "name": "Team B", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
"sideswitch": false,
"referenceMirrored": false,
"print": "0:0",
"teams": ["Arheilgen", "Prechtal"]
}
```
### Team-Datenbank
| Methode | Endpunkt | Body | Beschreibung |
|---|---|---|---|
| GET | `/admin/dbGetValues` | — | Alle gespeicherten Teamnamen abrufen |
| POST | `/admin/dbAddTeam` | `{ teamName }` | Teamnamen hinzufügen (Duplikate und Leerstrings werden ignoriert) |
| POST | `/admin/dbDeleteTeam` | `{ teamName }` | Teamnamen löschen |
Antwortformat:
```json
{ "teams": ["Arheilgen", "Prechtal"] }
```
---
## 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 |