-
Notifications
You must be signed in to change notification settings - Fork 3
Subsystem WS Server
host/pythings/websock_server/ws_server.py — a thin fan-out server that delivers daemon state to every connected browser at up to 30 Hz.
A single endpoint at ws://<host>:8090. No path routing; clients just open the WS and start receiving JSON state blobs. There's no client → server message format — it's a one-way push.
Each message is one of:
- A full state blob containing the entire
fw_state(parsed/data/state),fw_cursor(current playback time),fw_firing(currently-firing markers),fw_system(CPU/mem/temp), andfw_d_error(last 5 lines of/data/log/daemon.err). - A heartbeat of the form
{ "_hb": true, "fw_last_update": <ms> }if nothing has changed within 5 seconds. The browser uses this to keep its "Daemon fresh" indicator green.
Three sources, merged on every tick:
-
The Unix datagram socket
/tmp/byh_state.sock— the fast path. The daemonsendtos this socket every time state changes (debounced ~10 ms). The WS server reads, parses once, stashes inLATEST_FW_STATE, and notifies the broadcast loop via athreading.Condition. Sub-millisecond delivery from daemon → WS → browser. -
watchfiles.awatchon/data/— the fallback for/data/state. Triggers when the daemon does its atomic file replace (os.replaceaftermkstemp). This catches state pushes the WS server might miss if it wasn't bound to the socket yet (e.g. WS server still starting). -
watchfiles.awatchon/tmp/— auxiliary watcher for/tmp/fw_cursor(current show time) and/tmp/fw_firing(currently-firing items). The cursor file is rewritten ~1 Hz during a show; the firing file is rewritten when items start/stop firing.
It also reads CPU/mem/temp via psutil once per broadcast (cheap, no separate watcher).
The broadcast loop:
- Wait on the state condition (signaled by any of the three watchers).
- Build the merged payload via
_build_payload(). - If the rendered JSON differs from the last sent JSON, push to every connected client.
- If nothing changed but it's been more than
HEARTBEAT_FORCE_SECONDS(5 s), push a heartbeat. - Rate limit: at least
MIN_SEND_INTERVAL_S(1/30 s) between pushes.
So worst-case latency from "daemon writes new state" → "browser sees new state" is bounded by:
- ~10 ms (daemon debounce window) +
- ~0.1 ms (Unix socket round trip) +
- ~33 ms (broadcast rate cap).
Way under human-perceptible. During an active show, telemetry feels instant.
{
"fw_state": {
/* contents of /data/state — see PC daemon -> "State publishing" */
"device_running": true,
"show_loaded": true,
"loaded_show_id": 42,
"show_running": false,
"receivers": { "RX146": { "label": "...", "type": "...", "status": {...} } },
"settings": { "led_brightness": 10, "rf": { "current_channel": 76, ... } },
"ota": { "phase": "idle", ... },
/* ... */
},
"fw_cursor": -1.0, /* current show time in seconds, or -1 if idle */
"fw_firing": null, /* { itemId: { firing_until_ms } } during a show */
"fw_system": {
"cpu_pct": 12.4, "mem_pct": 38.0, "temp_c": 51.2
},
"fw_d_error": ["[2026-05-14 00:33:12Z] Some error message", ...],
"fw_last_update": 1715680123456,
"daemon_active": true /* derived from daemon_lup freshness */
}StatusBar.jsx is the only WS subscriber. On message:
- If
_hb→patchStateData({ fw_last_update })only. - Otherwise →
setStateData(payload)(replace).
The data then flows through useStateAppStore to every component that consumes it (useAppMode, ReceiverDisplay, ShowControl, etc.).
- WS server crashes: clients reconnect with exponential backoff. The "Link" indicator in the status bar goes yellow.
-
Daemon stops writing: the WS server keeps sending heartbeats (to prove it's alive itself). The "Daemon" indicator goes yellow once
fw_last_updateis older than ~5 s. - Browser tab loses focus: modern browsers throttle inactive tabs' WS, but the server doesn't care — it'll continue to send and the browser will buffer.
WS is the simplest API for "sub-second push from daemon to many browsers". SSE would work too but is harder to get right with reconnect logic. HTTP long-polling would quintuple the request overhead. The Unix-socket-to-WS hop is the pattern that gives both low latency and clean separation of concerns: the daemon knows nothing about HTTP/WS; the WS server knows nothing about the protocol handler.
Getting started
- Overview
- Desktop installers (macOS / Windows)
- macOS
- Linux
- Windows
- Production vs Development
- Connecting the dongle
- Flash a receiver
- Flash a dongle
- OTA flashing
Raspberry Pi
System overview
Subsystems
Hardware
- Receiver firmware
- Dongle firmware
- RF protocol
- Contributor Portal — BOMs, schematics, and board resources
UI walkthrough
Reference
Downloads
- Firmware
- Installers
Module Build & User Guides
- Cue
- Receiver
- Dongle