Skip to main content
Version: 2.0

Video Integrations

Since access control and video surveillance often go hand in hand, integrations between these systems are particularly valuable to security dealers and their customers. An integration with PDK enhances the utility of your video surveillance system by allowing it to do the following:

  • Pair access events with corresponding video clips.
  • Send lock and unlock commands directly from camera views.
info

Note that we do not currently display video within the PDK platform. We find that most users prefer the video system to be the "single pane of glass" that they interact with most often.

Mapping Cameras

In order to pair access events with video or send commands from camera views, users must first create mappings between their cameras and PDK devices. This interface can take many forms, but ideally the user would be able to see the camera views (not just the camera names) when selecting the devices within view. The screenshot below shows the camera mapping interface from the DW Spectrum integration.

Mapping Cameras

It often makes sense to allow multiple cameras to be mapped to a single PDK device, since multiple cameras may be pointing at the same access point (e.g. to capture multiple angles). Conversely, it may also make sense to allow a single camera to be mapped to multiple PDK devices (e.g. if multiple access points are visible in a single camera view), though this may require additional effort when it comes to sending commands, since users would need to indicate which device the command is intended for.

Pairing Events

To begin streaming events from PDK to the video system, you'll need to subscribe to webhook notifications for specific events. When those events occur, notifications will immediately be sent to your cloud service for handling. However, with 85 events to choose from (some of which are overlapping), deciding which events to subscribe to is not a trivial task.

To help streamline the decision process, we’ve identified 20 events that are likely to coincide with meaningful video data, and we’ve divided them into two categories: mechanical events and contextual events, which can be used to filter out noise.

Mechanical Events

Mechanical events are emitted when fundamental mechanical changes occur. These events tell you what happened, but not why it happened. They often coincide with other events that provide additional context (but not always).

EventMessage
device.input.dps.closeddeviceName closed
device.input.dps.openeddeviceName opened
device.input.relay.offdeviceName locked
device.input.relay.ondeviceName unlocked

Contextual Events

Contextual events tell you why a particular mechanical event occurred (or didn’t occur), so they are often more useful than mechanical events alone. In the table below, we've also identify 2 critical events, which may be deserving of higher alert levels compared to other events.

EventMessageCritical
device.alarm.forceddeviceName forced alarm activated
device.alarm.forced.cleareddeviceName forced alarm deactivated
device.alarm.propped.offdeviceName prop alarm deactivated
device.alarm.propped.ondeviceName prop alarm activated
device.autoopen.offdeviceName auto open deactivated
device.autoopen.ondeviceName auto open activated
device.autoopen.override.offdeviceName auto open override deactivated
device.autoopen.override.ondeviceName auto open override activated
device.forceclose.offdeviceName DND deactivated
device.forceclose.ondeviceName DND activated
device.forceopen.offdeviceName force unlock deactivated
device.forceopen.ondeviceName force unlock activated
device.input.rex.ondeviceName REX activated
device.request.allowedholderName allowed access to deviceName
device.request.deniedholderName denied access to deviceName
device.request.unknownUnknown user denied access to deviceName

Event Handling

With all relevant events categorized as either mechanical events or contextual events, you can now use the following buffering logic to filter out noise.

  • If a mechanical event is received:
    • Wait for 6 seconds.
    • Check whether the buffer contains another event from the same device.
      • If the buffer does contain another event from the same device:
        • Drop the event.
      • If the buffer does not contain another event from the same device:
        • Forward the event to the video system.
    • Store the event in the buffer for 12 seconds.
  • If a contextual event is received:
    • Forward the event to the video system immediately.
    • Store the event in the buffer for 12 seconds.

Examples

In each example below, multiple events are received by the integration service, but only one event is forwarded to the video system. The most important events are always forwarded immediately, while less important events are either delayed by 6 seconds or dropped altogether.

Example 1

A person successfully presents a credential to a reader and passes through a doorway.

  1. The device.request.allowed event is received, indicating that a credential was presented and access was allowed. Since this is a contextual event, it is immediately forwarded to the video system and stored in the buffer for 12 seconds.
  2. The device.input.relay.on event is received, indicating that the door was unlocked. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.
  3. The device.input.dps.opened event is received, indicating that the door was opened. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.
  4. The device.input.relay.off event is received, indicating that the door was locked. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.
  5. The device.input.dps.closed event is received, indicating that the door was closed. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.

Example 2

A person passes through a doorway that is in an unlocked state due to an auto open rule.

  1. The device.input.dps.opened event is received, indicating that the door was opened. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does not contain an event for the same device, so this event is forwarded to the video system (with the appropriate timestamp) and stored in the buffer for 12 seconds.
  2. The device.input.dps.closed event is received, indicating that the door was closed. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.

Example 3

A PDK administrator manually unlocks a door from the pdk.io interface.

  1. The device.input.relay.on event is received, indicating that the door was unlocked. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does not contain an event for the same device, so this event is forwarded to the video system (with the appropriate timestamp) and stored in the buffer for 12 seconds.
  2. The device.input.relay.off event is received, indicating that the door was locked. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.

Example 4

A person forces a door open without presenting a credential.

  1. The device.alarm.forced event is received, indicating that the door was forced open. Since this is a contextual event, it is immediately forwarded to the video system and stored in the buffer for 12 seconds.
  2. The device.input.dps.opened event is received, indicating that the door was opened. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.
  3. The device.input.dps.closed event is received, indicating that the door was closed. Since this is a mechanical event, we wait for 6 seconds before checking the buffer. Upon checking the buffer, we see that it does contain an event for the same device, so this event is dropped and stored in the buffer for 12 seconds.

Sample Code

The following sample code shows how the logic outlined above can be implemented in TypeScript. Since this code is intended to provide a minimal example, no error handing has been implemented.

handler.ts
import buffer from "./buffer"
import eventTypes from "./event-types"
import { WebhookNotification } from "./types"

export async function handleWebhookNotification(notification: WebhookNotification) {
const category = eventTypes[notification.topic].category
const message = createMessage(notification)
if (category == "mechanical") {
// Wait for 6 seconds
await new Promise((resolve) => setTimeout(resolve, 6000))
// If the buffer does not contain another event from the same device
if (!buffer.has(notification.deviceId)) {
// TODO: Forward the event to the video system
}
} else if (category == "contextual") {
// TODO: Forward the event to the video system immediately
}
// Store the event in the buffer for 12 seconds
buffer.add(notification.deviceId, 12000)
}

function createMessage(notification: WebhookNotification) {
const { topic, deviceName, holderName } = notification
let message = eventTypes[topic].message
message = message.replace(/{deviceName}/g, deviceName)
message = message.replace(/{holderName}/g, holderName)
return message
}
buffer.ts
const buffer = new Map<string, number>()

function add(deviceId: string, bufferTime: number) {
buffer.set(deviceId, Date.now() + bufferTime)
}

function has(deviceId: string) {
// Clean the buffer
buffer.forEach((expiration, deviceId) => {
if (Date.now() > expiration) {
buffer.delete(deviceId)
}
})
// Check the buffer
return buffer.has(deviceId)
}

export default { add: add, has: has }
event-types.ts
import { EventTypes } from "./types"

const eventTypes: EventTypes = {
"device.input.dps.closed": {
category: "mechanical",
message: "{deviceName} closed"
},
"device.input.dps.opened": {
category: "mechanical",
message: "{deviceName} opened"
},
"device.input.relay.off": {
category: "mechanical",
message: "{deviceName} locked"
},
"device.input.relay.on": {
category: "mechanical",
message: "{deviceName} unlocked"
},
"device.alarm.forced": {
category: "contextual",
message: "{deviceName} forced alarm activated",
critical: true
},
"device.alarm.forced.cleared": {
category: "contextual",
message: "{deviceName} forced alarm deactivated"
},
"device.alarm.propped.off": {
category: "contextual",
message: "{deviceName} prop alarm deactivated"
},
"device.alarm.propped.on": {
category: "contextual",
message: "{deviceName} prop alarm activated",
critical: true
},
"device.autoopen.off": {
category: "contextual",
message: "{deviceName} auto open deactivated"
},
"device.autoopen.on": {
category: "contextual",
message: "{deviceName} auto open activated"
},
"device.autoopen.override.off": {
category: "contextual",
message: "{deviceName} auto open override deactivated"
},
"device.autoopen.override.on": {
category: "contextual",
message: "{deviceName} auto open override activated"
},
"device.forceclose.off": {
category: "contextual",
message: "{deviceName} DND deactivated"
},
"device.forceclose.on": {
category: "contextual",
message: "{deviceName} DND activated"
},
"device.forceopen.off": {
category: "contextual",
message: "{deviceName} force unlock deactivated"
},
"device.forceopen.on": {
category: "contextual",
message: "{deviceName} force unlock activated"
},
"device.input.rex.on": {
category: "contextual",
message: "{deviceName} REX activated"
},
"device.request.allowed": {
category: "contextual",
message: "{holderName} allowed access to {deviceName}"
},
"device.request.denied": {
category: "contextual",
message: "{holderName} denied access to {deviceName}"
},
"device.request.unknown": {
category: "contextual",
message: "Unknown user denied access to {deviceName}"
}
}

export default eventTypes
types.ts
export interface EventTypes {
[topic: string]: {
category: "mechanical" | "contextual"
message: string
critical?: boolean
}
}

export type WebhookNotification = {
topic: string
occurred: number
systemId: string
deviceId: string
deviceName: string
holderName?: string
}

Sending Commands

If your video system is capable of sending arbitrary HTTP requests from configurable on-screen buttons, you can utilize this functionality to send commands to devices. In this scenario, an HTTP request is sent from the video system to your integration, which verifies the request before sending the appropriate command to PDK.