Skip to main content

Camera Updates Publisher

The Camera Snapshot stream lets the operator UI stop polling /Cameras/operational and /CameraFilters/* every 10 seconds. Frontend.API serves one edge-cached fleet snapshot; this function supplies the changes in between so a tile refreshes the moment its camera captures, goes offline, moves, or gets an AI hit.

How it works​

PublishCameraUpdatesCosmosTrigger (Functions/CameraUpdates/CameraUpdatesPublisherFunction.cs) listens to the change feed of the camera container using the leases container with the camera-updates- lease prefix, the same pattern as AddOrchestrationCosmosTrigger. Every batch:

  1. Filters to dataType == "operational" documents (the container also holds static, global-settings and boresight documents) and types them through Newtonsoft, because the entities carry Newtonsoft property names such as lastMovedDate that System.Text.Json would not map.
  2. Hands them to CameraUpdatesPublisher, a singleton that builds one CameraUpdateMessage per camera (last version wins inside a batch) and invokes BroadcastCameraUpdate on Frontend.API's CameraHub through the existing ISignalRConnectionService.

Each delta carries the public operational fields, the camera's latest image URL / thumbnail / timestamp / latency read from the document itself (SingleImageStorageService now patches latestImageUrl, latestImageTimestamp and the two acquisition-rate fields onto the operational document in the same Cosmos patch as pan/tilt/zoom, so publishing does no storage reads), the shared publisherKey (FrontendApiOptions:ApiKey, which the hub verifies and strips) and statusKinds: which status fields flipped compared with the last version this process saw (offline, movement, ai, patrol). The hub fans non-empty statusKinds out to every operator as a public-safe status event; AI values themselves never leave the private overlay.

Latency: the change feed is polled. The trigger sets FeedPollDelay = 1000 (the extension default is 5 000 ms), so a delta reaches the hub within about 1 s of the Cosmos write plus the client's 300 ms batch window. With the default the tile view trailed the location page by 3-6 s (uat, 2026-09-28: p50 2.9 s, p90 4.8 s), because the location page's image arrives through the direct BroadcastImageToGroup call from SingleImageStorageService while the tile view waits for the next feed poll. An empty poll costs about 1 RU per lease, so 1 s polling adds roughly 4 RU/s for the whole camera container (one lease per physical partition, five today) — noise against a 10k RU/s partition share.

Failure policy: a publish problem is logged and swallowed. The trigger never throws, so the feed cannot stall behind one bad batch; a missed delta is healed by the client's next snapshot resync.

The switch​

Two gates, both must be on:

  • CameraUpdatesOptions:Enabled (App Configuration, default true) — deploy-time master.
  • SQL FeatureFlag row enable-camera-snapshot-stream — runtime switch, re-read every 30 s and shared with Frontend.API's snapshot builder and the frontend. Missing row = off.

With the flag off the trigger still runs and returns immediately, so the lease keeps advancing: turning the feature back on later starts from "now" with no catch-up storm. Rollback is therefore just IsEnabled = 0 on that row.

Options (CameraUpdatesOptions)​

KeyDefaultMeaning
EnabledtrueDeploy-time master
FeatureFlagPollIntervalSeconds30Flag cache lifetime

Volume​

One hub invoke per operational write: roughly the fleet's capture rate (tens per second). Azure SignalR only bills outbound messages, and a camera's delta only reaches the connections subscribed to it, so cost scales with what operators are looking at, not with how many people are connected.