-
Notifications
You must be signed in to change notification settings - Fork 3
System Architecture
A high-level map of every process and every piece of hardware in a running Backyard Hero install. If a section sparks a question, the Subsystems and Hardware pages have the deep dives.
┌──────────────────────────────────────────────────────────────────────┐
│ HOST COMPUTER │
│ │
│ ┌────────────────┐ ┌──────────────────────┐ │
│ │ Browser │◄────► │ Web App │ │
│ │ (operator) │ HTTP │ Next.js, port 1776 │ │
│ └────────────────┘ └──────────┬───────────┘ │
│ ▲ │ │
│ │ ▼ │
│ │ WebSocket ┌────────────┐ │
│ │ :8090 │ SQLite │ │
│ │ │ /data/ │ │
│ │ │ backyard… │ │
│ │ └─────▲──────┘ │
│ │ │ │
│ ▼ │ │
│ ┌────────────────┐ │ │
│ │ WS Server │ │ │
│ │ ws_server.py │ │ │
│ └────────┬───────┘ │ │
│ ▲ │ │
│ │ /tmp/byh_state.sock │ │
│ │ + /data/state file │ │
│ │ │ │
│ ┌────────┴────────────┐ │ │
│ │ PC Daemon │ ──────────►│ /tmp/d_cmd/*.json │
│ │ pc_daemon.py │ ◄──────────│ (drop box from API) │
│ │ + BYHProtoHandler │ │ │
│ │ + OtaFlashDriver │ │
│ └─────────┬───────────┘ │
│ │ TCP localhost:9000 │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ tcp_serial_bridge │ ◄─── runs OUTSIDE Docker │
│ │ (Python pyserial) │ (so the container can reach USB) │
│ └─────────┬───────────┘ │
└─────────────┼────────────────────────────────────────────────────────┘
│ USB-CDC, 115200 8N1
▼
┌─────────────┐
│ Dongle │ ESP32-S2 + nRF24L01+ (2.4 GHz) + 433 MHz TX
│ os4_dongle │ 3 switches: start/stop, arm, manual fire
└──────┬──────┘
│
┌─────┴────────────┬────────────────────┬────────────────┐
│ 2.4 GHz nRF24 │ 2.4 GHz nRF24 │ 433 MHz one-way│
▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ Receiver │ │ Receiver │ ... │ Bilusocn 4-ch │ │
│ RX146 │ │ RX152 │ │ (TX-only legacy) │ │
└────┬─────┘ └────┬─────┘ └──────────────────┘ │
│ │ │
▼ ▼ │
8-cue modules 8-cue modules │
(chained 1–16) (chained 1–16) │
│
The host components in dashed Docker box are all packaged in one image (os4ivmb/backyardhero). The serial bridge runs natively on the host so it can see USB.
host/byh_app/backyardhero/
A single-page React app served by Next.js. The whole UI is rendered behind one Next.js route (/) — every "page" in the nav (Console, Receivers, Editor, Loadout, Inventory, Manual, Settings) is a tab inside MainNav.jsx.
The browser does two things over the network:
-
HTTP to the Next.js server for REST routes under
/api/*(CRUD on shows, inventory, racks, receivers; trigger daemon commands; upload OTA firmware). -
WebSocket to
ws://<host>:8090for live state (every receiver's telemetry, daemon status, time cursor, error log).
See Subsystem: Web app.
host/pythings/pc_daemon/pc_daemon.py
The heart of the system. It:
- Holds the TCP connection to the serial bridge.
- Parses the dongle's per-second status frames into a per-receiver telemetry dict.
- Loads shows from SQLite, transforms the timeline into firing arrays, preloads them onto receivers via the dongle.
- Runs the show schedule, issues the
play/stop/pausetransport commands, handles 433 MHz cues itself. - Polls
/tmp/d_cmd/*.jsonfor commands dropped by the web app's API routes. - Watches three logical switches (start/stop, arm, manual fire) reported by the dongle, enforces the interlocks.
- Publishes a JSON state snapshot to
/data/state(debounced ~10 ms) and pushes the same JSON to the WS server's Unix datagram socket for low-latency delivery.
See Subsystem: PC daemon.
host/pythings/websock_server/ws_server.py
A thin fan-out layer. Listens on a Unix datagram socket for state pushes from the daemon, additionally watches /data/state, /tmp/fw_cursor, and /tmp/fw_firing via watchfiles, merges everything (plus host CPU/memory/temp via psutil) into one payload, and broadcasts to every connected browser at up to 30 Hz with 5 s heartbeats.
See Subsystem: WS server.
host/tcp_serial_bridge/tcp_serial_bridge.py
Runs as a host-native process (not in the container) so it can hold the USB serial port. Forwards bytes both ways between TCP localhost:9000 and the dongle's USB-CDC at 115200 8N1, with auto-reconnect when the dongle reboots.
host/data/backyardhero.db (mounted into the container at /data/)
Tables: Show, inventory, inventoryFiringProfile, racks, Receivers. Created and migrated automatically by src/util/sqldb.js on every Next.js start. See Subsystem: Database for the full schema.
host/config/systemcfg.json, mounted at /config/. Holds the active dongle port + baud, the active protocol, and protocol/type metadata. See Subsystem: Config.
host/pythings/inv_crawl/crawl_catalog.py and host/pythings/fp_gen/process_firing_profiles.py. Optional services launched on demand from API routes:
- The crawl pulls a curated shell catalog JSON from
backyard-hero.com. - The firing-profile generator downloads the audio of a YouTube link and detects shot timings using
librosa+scipy.signal.find_peaks.
- Operator clicks Launch in the Console.
- Browser POSTs
{ "type": "start_show" }to/api/system/cmd_daemon. - The Next.js handler writes that JSON to
/tmp/d_cmd/<timestamp>.json. - The daemon's
poll_command_dirthread reads the file (~500 ms), deletes it, and routes it toBYHProtocolHandler.run_show. - The handler runs the precheck, picks
show_start_time = now_ms + 25 s, sendsshowstart RX146 <epoch_ms> 0 <showId> 6to the dongle for every preloaded receiver. - The dongle queues a CLOCK_SYNC + SHOW_START packet for each receiver, transmitting on its scheduled poll slot.
- Each receiver records
showStartTime, setsstartReady=true, returns the new state in its next ACK payload. - The dongle pushes that as a
rxupdJSON line up to the daemon, which publishes the new state to the WS server, which pushes it to the browser. The Console UI shows "T-25.0". - At T-0, every receiver fires its first cue autonomously from its preloaded schedule. The host doesn't have to send anything.
- Telemetry (battery, continuity, success %) keeps flowing back at ~1 Hz via the per-second
statusaggregate.
That's the entire control loop.
| Path | Meaning |
|---|---|
/app/byh_app/ |
Next.js app (built; npm run start) |
/app/pythings/pc_daemon/ |
Daemon source |
/app/pythings/websock_server/ |
WS server source |
/data/ |
Persistent volume (./data on host): SQLite DB, state file, log files, OTA staging |
/config/ |
Config volume (./config on host): systemcfg.json
|
/tmp/d_cmd/ |
Drop-box for command files from the API |
/tmp/fw_cursor, /tmp/fw_firing
|
Show playback markers |
/tmp/byh_state.sock |
Unix datagram socket for daemon → WS push |
| Port | Bound by | Purpose |
|---|---|---|
| 1776 | Next.js (byh-app) |
Web UI + REST API |
| 8090 | WS server | WebSocket state stream |
| 9000 | tcp_serial_bridge (host-native) | TCP framing for USB serial |
docker-compose.yml exposes 1776 and 8090 on the host. 9000 is not exposed — it's reachable inside the container as host.docker.internal:9000.
- Read Glossary and terms for the vocabulary used everywhere.
- Read Show lifecycle for a temporal walkthrough of "what happens when you load and fire a show".
- Read End-to-end show example for a concrete two-receiver run.
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