A lightweight REST proxy for the CRG Scoreboard WebSocket. Runs alongside the scoreboard software and re-exposes live game state as a simple HTTP endpoint — no WebSocket knowledge required.
The CRG scoreboard broadcasts live data (scores, clocks, jammer info) over WebSocket. This proxy connects to that feed and makes it available as a plain GET /live JSON endpoint that any custom overlay or display software can poll.
- Python 3.10+ (3.13 recommended)
- CRG Scoreboard already installed, running, and accessible (default port 8000; host/port configurable via flags)
If you have Chocolatey, open PowerShell as administrator:
choco install python -yOr download directly from python.org.
Verify:
python --versionLaunch the scoreboard the normal way (double-click the provided .exe or .bat file, or run java -jar crg-scoreboard.jar). Confirm it is accessible at http://localhost:8000 before continuing.
cd derby-scoreboard-api
pip install -r requirements.txt
python main.pyThe API is now available at http://localhost:5001.
To update with minimal interruption, this repo includes:
updater.config.json(single config file to choose update mode and settings)Run-Updater.cmd(double-click entry point that reads config and picks mode)scripts/(all automation scripts — you can ignore these)
For non-technical users, this is the only workflow you need:
- Open
updater.config.json - Set
modetomanualorauto - Double-click
Run-Updater.cmd
What it does:
- Runs
git pull --ff-only - Installs Python dependencies from
requirements.txt - Starts a standby API backend on an alternate port
- Swaps the stable proxy target to the healthy standby backend
- Stops the old backend after cutover
- Writes logs to
logs/updater/
- Public API (stable):
5001viaproxy.py - Backend A:
5002 - Backend B:
5003
The proxy reads runtime/active_backend_port.txt and forwards requests to whichever backend port is currently active.
- Ensure
git,python, andpipare available in PATH. - Put a shortcut to
Run-Updater.cmdon the Desktop. - Ensure port
5001is free for the stable proxy listener.
On first run, the updater starts both proxy and backend processes automatically.
Double-click Run-Updater.cmd.
Set mode in updater.config.json:
manual: runs one blue/green update nowauto: starts continuous watcher mode
Tip: keys that start with _comment_ are just helper notes for humans and are ignored by the launcher.
After a successful manual update, the launcher prints the Health and Live URLs using the current machine hostname so you can share them with the client team.
Example config:
{
"mode": "manual",
"branch": "main",
"checkIntervalSeconds": 120,
"scoreboardHost": "localhost",
"scoreboardPort": 8000,
"healthUrl": "http://localhost:5001/health"
}Advanced (PowerShell):
./Run-Updater.ps1If you want unattended updates, this repo includes optional tools:
Auto-Update-API.ps1(inscripts/; watchesorigin/mainand triggersUpdate-API.ps1only when new commits exist)Start-AutoUpdate.cmdreplaced byRun-Updater.cmdwithmode: autoin configStop-AutoUpdate.cmdinscripts/; stops a running auto watcherInstall-AutoUpdate-Task.cmdinscripts/; creates a Windows Scheduled Task that starts watcher on bootUninstall-AutoUpdate-Task.cmdinscripts/; removes that task
- Checks the local branch name (must be
main) - Runs
git fetch origin main --prune - Computes commits behind with
git rev-list --count HEAD..origin/main - If behind > 0, runs the blue/green swap updater
- Repeats every 120 seconds (default)
Set "mode": "auto" in updater.config.json, then either:
- double-click
Run-Updater.cmd, or - double-click
Start-AutoUpdate.cmd.
When you are done, run Stop-AutoUpdate.cmd.
Run Install-AutoUpdate-Task.cmd as Administrator once.
This creates task DerbyScoreboardAPIAutoUpdate, starts it immediately, and runs it automatically at Windows startup.
Run Uninstall-AutoUpdate-Task.cmd.
./Auto-Update-API.ps1 -RunOnce./Auto-Update-API.ps1 -CheckIntervalSeconds 60 -Branch mainAuto-updater logs are written to logs/autoupdater/.
All implementation scripts live in scripts/. Most users should ignore that folder:
scripts/Update-API.ps1andscripts/Auto-Update-API.ps1: core blue/green and watcher logicscripts/Run-Updater.ps1: config-driven launcher called byRun-Updater.cmdscripts/Stop-AutoUpdate.cmd: stops a running auto watcherscripts/Install-AutoUpdate-Task.cmd/Uninstall-AutoUpdate-Task.cmd: optional always-on Windows task
Options:
| Flag | Default | Description |
|---|---|---|
--scoreboard-host |
localhost |
Hostname/IP of the CRG scoreboard |
--scoreboard-port |
8000 |
Port the scoreboard is running on |
--host |
0.0.0.0 |
Host to bind the API server to |
--port |
5001 |
Port to serve the API on |
Remote scoreboard example:
python main.py --scoreboard-host 192.168.1.50Clean, mapped live game state. Poll this at whatever rate suits your overlay (200ms is smooth for clocks).
{
"connected": true,
"period": 1,
"jam": 4,
"jam_clock_ms": 89000,
"period_clock_ms": 412000,
"jam_running": true,
"in_jam": true,
"game_state": "Running",
"timeout_type": null,
"state_age_seconds": 0.1,
"team1": {
"name": "Home Team",
"score": 42,
"jam_score": 5,
"jammer": "Speed Demon",
"jammer_number": "88",
"lead": true,
"display_lead": true,
"calloff": false,
"lost": false,
"star_pass": false
},
"team2": {
"name": "Away Team",
"score": 37,
"jam_score": 0,
"jammer": "Lightning Bolt",
"jammer_number": "7",
"lead": false,
"display_lead": false,
"calloff": false,
"lost": false,
"star_pass": false
}
}Clock note: All
*_msfields are in milliseconds. E.g.89000= 1 minute 29 seconds.
connected:truewhen the proxy has an active WebSocket connection to the scoreboard. Iffalse, the proxy is reconnecting andstate_age_secondstells you how stale the data is. Overlays can use this to show a "RECONNECTING" indicator without a separate call to/health.
state_age_seconds: Seconds since the proxy last received an update from the scoreboard.nullmeans no update has been received yet (proxy just connected). If this grows above a few seconds whileconnectedistrue, the scoreboard may be frozen.
game_state: Usually mirrors CRG's rawStatefield, but may be normalized for display use. For example, ifClock(Intermission).Runningistrue,/livereturns"Intermission"even if the raw scoreboardStatestill says"Running".
timeout_type: Normalized timeout/review state derived fromgame_state. Values:team_timeout,official_timeout,official_review,timeout, ornull. It is forced tonullwhenjam_runningistrue(play resumed).
Full flat state dict as received from the scoreboard WebSocket. Useful for discovering all available fields or debugging.
Connection status:
{"connected": true, "scoreboard_version": "v5.0.0", "seconds_since_update": 0.3}seconds_since_update is null until the first update is received. A large value while connected is true indicates the scoreboard may be frozen.
Auto-generated interactive OpenAPI docs (Swagger UI).
The proxy is designed to stay running no matter what:
- If the scoreboard isn't running when the proxy starts, it will keep retrying every 2 seconds until it connects.
- If the scoreboard restarts mid-game, the proxy reconnects automatically.
GET /livereturns HTTP 503 while disconnected so pollers know to wait rather than display stale data.GET /healthalways returns, even when disconnected — use it to monitor connection state.- Unhandled errors in any endpoint are caught and logged without killing the process.
See EXTENDING.md for a guide on adding new fields, endpoints, and more.
For a client-facing summary of requested scoreboard fields, current /live coverage, and missing timeout/review data needed for broadcast UI, see CLIENT_FIELD_CROSSWALK.md.
pytestTests use a mock WebSocket server — no real scoreboard needed.
- On startup, a background asyncio task connects to
ws://<scoreboard-host>:<scoreboard-port>/WS/ - Subscribes to
ScoreBoard.CurrentGameto receive all live game state - Maintains an in-memory state dict, applying incremental patches as the scoreboard broadcasts them
GET /livereads from that dict and maps raw CRG key names to clean JSON fields via a declarative field map- Auto-reconnects with a 2-second delay on any disconnect or error
The proxy uses the CRG scoreboard's native WebSocket API:
- Endpoint:
ws://host:8000/WS/ - Subscribe:
{"action": "Register", "paths": ["ScoreBoard.CurrentGame"]} - Updates:
{"state": {"ScoreBoard.CurrentGame.Clock(Jam).Time": 89000, ...}} - A
nullvalue means that key was deleted/reset - Full protocol docs available at
http://scoreboard-host:8000/documentation/wiki-snapshot.html - Live key browser at
http://scoreboard-host:8000/json/state.html