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.
A main and a substream per camera, pulled by the hub. Reolink today; a vendor driver is a shared object.
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
events.jsonl as the searchable record.Measured, with the box it was measured on
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.