§7e DCC bus daemon (dcc-bus)
This section specifies the dcc-bus sibling daemon that owns one
DCC connection to a command station (centralka) in the context
of one layout (makieta) and exposes a dedicated WebSocket port
to the frontend for the throttle (tryb sterowania) data plane.
It is the runtime counterpart of the Throttle UI specified in §6.3b.
Until §7e is implemented, the throttle dispatch from §5.3 –
LocoService.SetSpeed / LocoService.ToggleFn – lives inside
loco-server itself, sharing the WebSocket Hub with takeover, radio,
scripts and presence. §7e splits the data plane out of loco-server
into a per-(layout, command_station) daemon. Authorization rules,
domain model, REST surface and security policies (§7a.3) are unchanged
– dcc-bus carries its own throttle policy in pkgs/bigfred/dcc-bus/security
(DrivePolicy, TrainPolicy); server-side §7a.3 policies stay on
loco-server. Only the location of the DCC bus and the delivery
of throttle traffic move.
Overview & design goals
Why a sibling daemon, the (layout × command station) cardinality rule, control plane vs. data plane split.
Process model, CLI & supervisord wiring
dcc-bus cobra command, flags, port allocation, lifecycle under
SupervisordService (§7d).
State & Redis cache
Vehicle subscription set per layout, polling, key layout for the loco-state hash, command channel for server-initiated DCC writes (scripts, takeover, dead-man).
WebSocket protocol
The throttle action set hosted by dcc-bus (loco.*, system.estop, ping),
how it relates to loco-server's WS (session.*, takeover.*, radio.*,
script.*).
Authorization & session awareness
JWT validation in dcc-bus, pkgs/bigfred/dcc-bus/security drive policy,
takeover / lease propagation via Redis pub/sub, dead-man's switch scoped to the
daemon.
Server integration & orchestration
How loco-server decides which dcc-bus programs must run, port assignment,
hot reload via supervisord, server-initiated DCC commands (scripts, dead-man,
takeover release), session.opened payload extension.
Frontend integration
Dual-WebSocket model in the browser, throttle overlay (§6.3b) wiring,
command-station dropdown, reconnect logic, the Throttle component tree.
Acceptance criteria
Externally observable behaviour the milestone must demonstrate.
External state observation
Reflecting speed/direction/function changes made by external physical
throttles; driver subscription-vs-polling research, the
commandstation.StateObserver capability, and Z21 drive encoding
(direction-at-stop, SET S=3 vs. INFO KKK=4).
Quick reference
| Concern | Decision |
|---|---|
| Process cardinality | One dcc-bus per (layoutId, commandStationId) pair that has at least one active drive session pinned to it. Lazy-started by loco-server; long-lived until shutdown. |
| Binary | The loco-server Go binary exposes a dcc-bus cobra subcommand (single binary, multiple main entry points — same pattern as scripts-executor §7d). |
| Imports | pkgs/loco/commandstation (DCC), pkgs/bigfred/server/domain (entities), pkgs/bigfred/contract (Redis wire types), pkgs/bigfred/dcc-bus/{state,cmd,service,security,…} — see §3.0 / repository tree. Does not import pkgs/bigfred/server/repo, pkgs/bigfred/server/http, pkgs/bigfred/server/ws, or open SQLite. cmd does not import dcc-bus/ws (adapter lives in ws). |
| Listener | Plain http.Server upgraded with coder/websocket on the CLI-supplied port. Binds to 127.0.0.1 by default; loco-server may reverse-proxy it. |
| Authentication | JWT issued by loco-server (shared secret); ?token= query parameter on the WS upgrade, identical to §7a.1. Verifies that the JWT's layoutId matches the daemon's --layout-id. |
| Authorization | Drive commands are gated by in-memory roster snapshots from Redis (allowed_vehicles → controllerUserIds per DCC address). Lease / takeover expansion of controllerUserIds is planned; today the server publishes the vehicle owner. |
Coordination with loco-server |
Redis only between processes, typed by pkgs/bigfred/contract: loco:state cache, roster snapshots (bigfred:layout:<L>:allowed_vehicles, defined_trains), command/event pub/sub. Command-station connection parameters are passed on the supervisord command line (--station-*). Catalogue truth stays in loco-server's SQLite. |
| Throttle WS actions | loco.subscribe, loco.unsubscribe, loco.setSpeed, loco.toggleFn, train.setSpeed, system.estop, ping. All other WS actions stay on loco-server's /api/v1/ws. |
| Dead-man's switch | Per-daemon: each dcc-bus runs the heartbeat for its own WS clients and executes the user's EmergencyPlan against drive targets on its own command station only (§4.5 still applies for cross-cutting concerns like scripts). |
| Supervisord group | dcc-bus (alongside loco for scripts-executor); programs named dcc-bus-<layoutId>-<commandStationId>. |
| Failure isolation | A crashing dcc-bus does not bring down loco-server; supervisord (§7d) restarts it with autorestart=true. Affected throttles see a stale-cache banner and re-connect on RUNNING. |