Skip to main content

Camera Movement & Leasing

Two dispatchers are watching the same ridge camera during a wind event, and both want to point it at different smoke. A camera can only look one way. The lease is how the platform decides whose hands are on the wheel — and this API is the authority that decides it. (The frontend's lease deep-dive covers the client's view; this is the server that actually grants and expires the lease.)

The one idea to hold on to​

The lease is granted as a side effect of a completed move, not requested — and the only hard gate is the Camera.Operate role. A PTZ request doesn't create a lease. It queues the move to the ImageAcquisition engine and returns; the engine executes it and then calls back, and that callback records the lease:

// PanTiltZoomCamera → just queues the move to the engine
await _httpClient.PostAsync($"{_baseUrl}/QueuePanTiltZoomCamera", ...);
// later, the engine calls FunctionEndpoints/leaseCameraAfterMove →
var lease = await _cameraLeaseRepository.ExecuteLeaseProcess(leaseInsert);

So there's a real ordering: move accepted → engine moves → lease recorded. Meanwhile, GET /lease/{cameraId} reports the current holder — which the frontend uses to warn a second operator, not to block them. Play the arbiter's side of it:

🎟️ Lease arbiter (server view)step 1/7
current holderfree
expires in—
request log…

No lease. The camera is free; anyone with the Camera.Operate role can move it.

Three things that sequence makes concrete: the lease is a side effect (the /ptz call returns before it exists), Camera.Operate is the only hard gate (a viewer gets a 403, everyone with the role gets through), and the lease is advisory — a second operator with the role can move anyway, and the recorded holder simply becomes them.

Gotcha: the lease warns, it does not lock

Nothing on the server rejects a movement because someone else "holds" the camera. The GET /lease result exists so the client can show a warning; the backend arbitrates by recording whoever actually moved last. Two operators confirming at the same instant both succeed. If you're expecting the API to enforce single-writer exclusivity, it doesn't — and by design. The role check is the only thing that turns a request away.

Why grant-as-side-effect​

Recording the lease only after the engine confirms the move keeps the record honest: a lease means a move actually happened, not that someone asked. It also means the API stays a thin, fast queue-and-return for the common case, and the durable, replay-safe part (the actual camera control) lives in the engine where it belongs. The cost is the ordering subtlety above — the /ptz response can't hand back a fully-formed lease, because it doesn't exist yet.

Wrapping up​

The lease protocol's authority is this API, but the mechanism is indirect: /ptz queues the move to the engine and returns, the engine calls back to record the lease via ExecuteLeaseProcess, and GET /lease reports the holder for the client to warn on. The role is the only hard gate; the lease itself is advisory. Movement, extend, close, and home round out the lifecycle.

Where does the role — and the bearer token carrying it — come from? Next: Authorization.