The architecture

One hub. One bus. Everything else subscribes.

The core, cctv_hub, is one epoll thread on raw syscalls with no libc. It ingests the cameras over RTSP and fans every stream out to whoever asked for it. Nothing else in the system talks to a camera, and no subscriber knows what the others are doing.

Ingest
8 cameras · 16 RTSP streams

A main and a substream per camera, pulled by the hub. Reolink today; a vendor driver is a shared object.

driveway
terrace
pergola  3.33:1
… 8 live, 2 planned
cctv_hub — the event bus
one epoll thread · no libc · ~2.4% of a core for 16 streams

Every frame, every event, every command and every outcome crosses here — and nowhere else. Facts, commands and outcomes all travel the same documented line protocol, which is why adding a consumer never means touching the ingest path.

Recorder

Motion-gated event clips: preroll, episode, postroll, contiguous.

Detector

Object detection on your own hardware, with a novelty tracker — so presence is not an event.

Camera control

One vendor-neutral vocabulary, with vendor shapes isolated in a driver.

Watchdog

A camera going dark is a first-class event with alarm rules, not a gap you find next week.

Also on the list: the live wall, the phone app, the exporter, the console — each one a process that asked the bus for what it needs. So is anything you write.

What travels on the bus

Frames. Annex-B video, as it arrived, to every subscriber that asked.
Events. Motion, detections by class, camera state, job progress. Written to events.jsonl as the searchable record.
Commands. One vendor-neutral vocabulary — reboot, PTZ, encoder, image profile — never a vendor's own shape.
Outcomes. What happened to each command, as an event anyone can act on.

Measured, with the box it was measured on

Whole stack, production footprint ~1.0 of 20 cores · <1 GiB QNAP TVS-h874 · 8 cam / 16 streams
Hub ingest cost ~2.4% of a core Same box · 16 RTSP streams
Bus latency, p95 0.064 ms · 0.210 ms TLS Loopback · debug build
Transport floor UDS ~5× loopback TCP Synthetic · 1 KB frames

Absolutes are box-dependent: ratios travel, absolutes do not. Every figure here names its machine and its build so you can repeat it on yours.

The fleet summary screen: per-class event counts across the fleet, rendered from the same event index the recorder writes. A design render, not a capture.
The same event index, rendered for a human · design render