Skip to main content

Two Machines, One Camera

A wildfire camera never stops. It captures on a schedule, swings to panorama, obeys an operator who grabs it to point at smoke, and it has to keep doing all of that for months without anyone restarting it — across ~1,400 cameras at once, surviving deploys, crashes, and scale-outs. You cannot hold that in a while (true) loop in memory. The acquisition engine runs it on Azure Durable Functions, and the shape of that solution is the thing to learn first, because everything else in this section is a consequence of it.

The one idea to hold on to​

Each camera is run by two cooperating machines, not one:

  • an eternal orchestration — the loop that decides what to do this cycle (capture, move, transition modes) and then schedules itself to wake up again, and
  • a durable entity (CameraState, keyed by device id) — the camera's memory: its current mode, PTZ position, the queue of pending moves, lease timers.

The orchestration is stateless between cycles; the entity is the state. Every loop, the orchestration reads the entity, acts, signals changes back to it, and hands off. Neither one alone is "the engine" — the engine is the two of them talking:

Hold that pairing. The next lesson is about why the loop half has to be so careful, and the one after that is about the queue living in the entity half.

The three states of one camera​

If the entity is runtime memory, where does the camera's configuration live — its rates, whether it's a pano camera, its location? And how does a dashboard show a camera's status without reaching into a running orchestration? A single camera is actually described by three states, each in a different store, each with a different job:

🎛️ Three states, one camerastep 1/5
IntendedAcquisitionDeviceEntity · Cosmos10s
RuntimeCameraState · durable entity10s
MonitoringDurableEntityData · Table Storage10s
Downstream: Admin dashboards · CameraOperation

Steady state: the intended config (Cosmos), the runtime entity, and the monitoring row all agree on a 10s rate.

  • Intended state — AcquisitionDeviceEntity in Cosmos DB. The desired configuration, owned by the Admin API. The orchestration receives it as input and carries it forward via ContinueAsNew, so a Cosmos edit does nothing until a redeploy restarts the orchestration with the new document (that's what the change-feed trigger is for — a later lesson).
  • Runtime state — the CameraState entity. What's true right now: the active mode, the command queue, the last PTZ position, the lease/turbo timers. This changes every loop.
  • Monitoring state — DurableEntityData in Table Storage. A flattened mirror the engine writes each loop (SetEntityState). Downstream services — Admin dashboards, CameraOperation's "running & online" endpoint — read this, so they can report status without ever calling the orchestration or the entity.
Gotcha: don't read runtime state to answer a status question

It's tempting to query the durable entity to find out what a camera is doing. Don't — that's what the monitoring mirror is for. Querying entities at fleet scale competes with the orchestrations for the task hub and is exactly the load the third state exists to prevent. If a dashboard needs status, it reads DurableEntityData from Table Storage.

Wrapping up​

The engine is two machines per camera: an eternal orchestration (the loop) and the CameraState durable entity (the memory), in constant conversation. Around them sit three states — intended config in Cosmos, live runtime in the entity, and a monitoring mirror in Table Storage that exists so downstream can read status without touching the engine. Keep the intended-vs-runtime distinction in mind: config changes need a redeploy to take hold.

"Torn down and resumed" is doing a lot of work in that sentence. Before anything else, it's worth understanding exactly how Durable Functions pulls that off — because every rule of the engine follows from it. Next: Replay, Explained.