No description
Find a file
Julian Appel 6f4c63aa6e Bugfixes: Timer-Zuschlag, Debugfenster-Schalter, Score-Untergrenze
timerIncDec beendete den Timer auch beim Hinzufuegen von Zeit: die Pruefung
verglich den Betrag des Werts mit der Restzeit, ohne die Richtung zu beachten.
Bei 5s Restzeit fuehrte "+10s" damit zum Spielende statt zu 15s. Die Pruefung
gilt jetzt nur noch fuer negative Werte. Beim Verringern unter 0 wird die
Restzeit zusaetzlich sauber auf 00:00 gesetzt, sonst blieb sie stehen und der
Timer war trotz "ENDE" auf dem Monitor wieder startbar.

Im Debugfenster zeigten alle drei Label auf scoreSwitchEnable. Ein Klick auf
den Text "Teamanzeige spiegeln" oder "Splashscreen anzeigen" blendete damit das
Scoreboard aus. Jede Beschriftung zeigt jetzt auf ihren eigenen Schalter.

scoreSetScore sendet den neuen Stand jetzt wie alterScore und clearScore an die
Monitore; der Endpunkt wird vom Panel nicht verwendet, ist aber dokumentiert.

alterScore laesst den Spielstand nicht mehr unter 0 laufen.

Die toten Funktionsprototypen in timer.js und score.js sind entfernt: sie wurden
durch Hoisting ohnehin ueberschrieben und listeten toggleReferenceMirrored und
print schon nicht mehr auf. Ebenso die Wert-Snapshots duration, durationLeft und
isPaused aus module.exports, die dauerhaft die Startwerte lieferten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 21:06:30 +02:00
scoreboard Bugfixes: Timer-Zuschlag, Debugfenster-Schalter, Score-Untergrenze 2026-08-19 21:06:30 +02:00
.gitignore Added socketio, and moment 2024-04-21 12:48:59 +02:00
README.md README auf den gemergten Stand gebracht 2026-08-19 20:39:27 +02:00

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

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:

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:

{ "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:

{
  "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:

{
  "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:

{ "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