Pattern 16: Video Event Mapping¶
CCTV adapters translate vendor camera/NVR events into PQ domain events under the pq.event.video.* and pq.event.technical.* namespaces. This pattern covers the full event taxonomy, the alert+poll duality, stale analytics events, and NVR-level vs channel-level routing.
PQ Video Event Taxonomy¶
Channel-level events (emitted on the Channel Thing)¶
| PQ Event | Severity | When to emit |
|---|---|---|
pq.event.video.motion.detected |
Info | Motion starts (VMD or PIR) |
pq.event.video.motion.cleared |
Info | Motion ends (VMD inactive) |
pq.event.video.blind |
Warn | Camera offline / video signal lost |
pq.event.video.blind.cleared |
Info | Camera back online / signal restored |
pq.event.video.quality.degraded |
Warn | Tamper, shelter, defocus, bad video |
pq.event.video.quality.restored |
Info | Quality event ended |
pq.event.video.analytics.object_detected |
Info | Line crossing, region entrance/exit, face detection, scene change, baggage detection |
pq.event.video.recording.failed |
Error | Recording failure on this channel |
pq.event.video.preset.activated |
Info | PTZ preset goto completed |
pq.event.video.preset.stored |
Info | PTZ preset position saved |
pq.event.technical.fault.sensor |
Warn | Audio exception (use sensorType parameter) |
NVR-level events (emitted on the NVR Thing)¶
| PQ Event | Severity | When to emit |
|---|---|---|
pq.event.technical.fault.hardware |
Error | Disk full, disk error, storage failure, illegal access attempt |
Catch-all¶
| PQ Event | Severity | When to emit |
|---|---|---|
pq.event.device.unknown |
Warn | Any vendor event that does not match a known mapping |
Use the eventType parameter to carry the original vendor event identifier so the unknown event is not silently lost.
Event Parameters¶
Some events carry additional context via parameters:
| Event | Parameter | Example values |
|---|---|---|
pq.event.video.motion.detected |
reason |
PIR (when triggered by PIR sensor, not VMD) |
pq.event.video.quality.degraded |
reason |
vendor event type string |
pq.event.video.analytics.object_detected |
objectType |
vendor event type string |
pq.event.technical.fault.hardware |
reason |
vendor event type string |
pq.event.technical.fault.sensor |
sensorType |
audio |
pq.event.device.unknown |
eventType |
{protocol}:{vendorType}:{state} |
Alert + Poll Duality¶
Video loss (camera offline) can arrive through two independent code paths:
| Path | Trigger | Latency |
|---|---|---|
| AlertStream | NVR pushes videoloss event |
Near-real-time |
| Status poll | Periodic REST query for channel status | Up to poll interval |
Both paths must perform the same three actions:
- Set the
VideoSignalstatus slot tovideo.signal.loston the Channel - Tear down the go2rtc stream for that channel (see concepts/video-streaming.md)
- Emit
pq.event.video.blind
On recovery:
- Clear the
VideoSignalstatus slot (null) - Emit
pq.event.video.blind.cleared
The poll path also catches transitions that happen during an alertStream reconnect window, so both paths are necessary even if the alertStream is reliable.
Stale Analytics Events¶
Many analytics event types (line crossing, region entrance, face detection, etc.) send only an "active" notification — they never send an "inactive" or "cleared" counterpart. If not handled, the event remains in active state indefinitely in PQ.
The problem: after the object leaves the scene, no event arrives to clear the state.
The pattern:
- On receipt of an analytics "active" event, record the current time and the
activePostCountfield from the event (if the vendor provides it) - Start a short timer (e.g., 5 seconds)
- On timer expiry, if no subsequent "active" event for the same channel + type has arrived, emit the corresponding cleared event
This is a "last-seen" timeout pattern. The timer is reset each time a new active event arrives for the same rule, so continuous detections do not spam cleared/detected transitions.
Current state in the reference implementation:
StaleEventTimeout(5 s) andactivePostCountare defined as constants and tracked in the parsed event record, but the timeout-based clear mechanism is not yet implemented. Analytics events remain in active state until the channel goes offline. This is a known gap.
Channel 0 Suppression¶
NVRs typically emit system-level events on a virtual channel with ID 0. This channel does not correspond to any real camera. Events on channel 0 must be consumed and suppressed — do not emit a PQ event and do not log a warning.
// Suppress before any other route
router.When(a => a.ChannelId == 0)
.WithAddress<NvrType>((_, owner) => owner as NvrType)
.Handle((_, _) => Task.FromResult(true));
Place this route first so it takes priority over any catch-all.
NVR-level vs Channel-level Routing¶
Route events to the correct Thing:
| Event origin | Route to | Rationale |
|---|---|---|
| Camera-specific (motion, tamper, video loss) | Channel Thing | The event is about a specific camera |
| NVR hardware (disk, storage, illegal access) | NVR Thing | The event is about the NVR itself, not any camera |
| Audio exception | Channel Thing | The audio sensor is on the camera |
For channel-level routing, resolve the Channel by its protocol address (channel ID from the event). For NVR-level routing, resolve directly to the NVR owner.
Catch-all Route¶
Always register an Unhandled handler so any vendor event without a matching route is visible in the audit log rather than silently dropped. Use owner.Unknown(...) — it applies the RULE-027 two-axis classification (device.unresolved when the address found no Thing, device.unknown otherwise); never hand-pick the event id via a raw EventBuilder.
router.Unhandled(async (owner, evt, reason, ct) =>
await owner.Unknown(
Ts(evt),
eventType: $"{protocol}:{evt.Type}:{evt.State}",
reason: reason,
rawData: evt.ToRawString(),
cancellationToken: ct));
See Also¶
- concepts/video-streaming.md — stream teardown on video loss
- patterns/19-stream-registration.md — the two-path teardown as part of the full register/reconcile lifecycle
- archetypes/cctv-nvr.md — overall CCTV adapter shape
- patterns/event-mapping-guide.md — systematic vendor event mapping process
- patterns/02-event-translation.md — event routing with EventRouter
- patterns/09-event-publishing.md — publishing PQ events from Things