Context
Event-photography studios run dozens of cameras at once — bullet-time rigs, multi-angle product shoots, 360° capture arrays. The tooling available to them fell into two camps: consumer software built for a single, manually-attended camera, or bespoke per-rig setups with no fault tolerance. Either way, a serious multi-camera shoot meant an operator babysitting every machine, cameras dropping mid-event with no recovery, and a hard dependency on venue IT that frequently didn’t exist.
CamDeck replaces that with a single operator console that controls roughly a hundred cameras over an isolated local network, fires them in near-lockstep, and ingests every capture through a pipeline that retries and reconciles on its own. One design constraint shaped every decision: it has to work in a venue with no internet, no router, and no IT support.
The problem, stated as constraints
- Fully offline. No cloud and no assumed router. Discovery and control happen on an isolated LAN that the system forms itself.
- One operator, ~100 cameras. The console has to surface every camera’s live state without scrolling fatigue, and a single trigger has to fan out reliably to the whole fleet.
- Cheap, replaceable nodes. A small single-board computer plus a USB hub costs under $100. The system is built to lose and rebuild a node rather than nurse a fragile one.
- An unsupported hardware platform. The camera vendor’s control SDK targets a different operating system and architecture than the low-cost nodes we deployed on. Making it behave reliably on our platform was a core part of the work.
What I built
The control path is four clean layers, each replaceable.
A few of the decisions I’m most happy with:
Self-forming network, zero configuration. The server broadcasts its address on the local network on a fixed interval. Camera nodes boot, listen, and join the fleet automatically — no DNS, no static config, no manual pairing. A node can be unplugged from one venue and plugged into another, and it simply re-joins. That property is what makes the system deployable by a photographer rather than a network engineer.
One process per camera. Each physical camera owns its own control process, supervised by the OS service manager. Sessions to the camera SDK aren’t safely shareable, and — just as important — one misbehaving camera must never take down the other ninety-nine. Crashes are isolated to a single body.
Zero-touch cold boot. When a node powers on (or a camera is hot-plugged), the system enumerates the attached cameras and brings each one online with no operator action. Boot-to-ready is about thirty seconds per node, and cameras auto-register into the console as they come up.
Don’t trust the acknowledgment — verify the frame. Early on, the system could report a successful trigger while a capture silently never arrived: the command was acknowledged, but the image never made it through. I added end-to-end delivery accounting so the server detects a “command acknowledged, frame missing” gap itself — during the shoot, not in the post-mortem. Treating an ack as a promise rather than proof was the single biggest reliability unlock.
Whole-fleet recovery from one command. When devices wedge, an operator runs a single self-heal command that resets stuck cameras across the entire fleet, rather than walking the room unplugging hardware.
Contracts at every boundary. Every edge between processes — HTTP, real-time sockets, inter-process calls, native calls — is validated against a shared schema, with unknown fields rejected. CI enforces that the running code, the API spec, and the socket contracts can’t silently drift apart. For a system with this many moving parts and no human watching most of them, strict contracts are what let me change one layer without fear.
Diagnosing failures across the software / hardware line
A lot of the hard-won lessons were about correctly attributing failures, because a flaky multi-camera rig produces symptoms that all look alike.
A never-settling capture, not a hang. One class of boot-time fault left capture operations that never resolved — easy to mistake for a frozen process. The fix was the delivery-accounting described above, which surfaces a stuck capture as a measurable gap instead of an invisible stall.
Power delivery vs. power supply. Two distinct hardware faults — voltage sag on cheap powered USB hubs, and node-level brownouts from marginal power supplies — present almost identically as “cameras randomly drop.” I built the diagnosis to tell them apart from the node’s own throttling signals, so each marginal node lands on the right watchlist and gets the right fix instead of being papered over in software.
Separating these cleanly is what turned “the rig is flaky” into a small set of named, individually-addressable faults.
Results
- ~30 nodes driving ~100 cameras online simultaneously under a single operator.
- Trigger latency p95 under 200ms end-to-end, from operator click to camera command dispatch.
- Eliminated mid-shoot capture loss — from double-digit percentages to zero across a full shoot, after the reliability stack landed.
- ~30-second boot-to-ready per node; cameras auto-register with no operator intervention.
- Single-command fleet recovery for stuck devices, replacing a manual walk-the-room reset.
What I’d carry into the next version
Runtime health detection, not just boot-time. Boot-time recovery is solid; the area I’d invest in next is detecting a process that goes unresponsive mid-shoot via heartbeat staleness, so runtime faults get the same automatic handling that boot faults already do.
A real component model for the console. Hand-managed DOM was exactly right at the start — no build chain, instant iteration. Past a dozen-plus cameras with rich per-camera state, a lightweight component layer would have paid for itself. I’d reach for one earlier next time, while still avoiding heavyweight framework overhead.
An append-only event log instead of state snapshots. Persisting whole-state snapshots works, but a small append-only log of events would unlock replay debugging and post-mortem reconstruction — “what exactly did the operator see at 14:32?” — for very little migration cost.
Treat hardware faults as first-class from day one. A modest up-front hub-and-power stress harness would have isolated the physical failure modes earlier and saved real debugging time. Building software resilience is no substitute for budgeting around the hardware you actually deployed on.
Built as a production system for live event capture. Figures above are paraphrased for a public audience; happy to go deeper on the architecture in conversation.