UNPKG

@komponent/unifi-protect-lib

Version:

Node library for connecting to Ubiquiti Unifi Protect controllers and listen for events

167 lines 7.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const UnifiApiUpdateUtil_1 = require("./UnifiApiUpdateUtil"); const Utils_1 = __importDefault(require("./Utils")); class UnifiCameraHandler { constructor(log, apiClient, config = { refreshInterval: 5 }) { this.log = log; this.apiClient = apiClient; this.lastMotion = {}; this.lastRing = {}; this.eventTimers = {}; this.config = config; } /** * Get the list of UniFi Protect cameras associated with the NVR. * * @return {*} {Promise<Array<Device>>} * @memberof UnifiApiClient */ async getCameras() { if (!this.apiClient.bootstrap) { return; } if (this.cameras) { return this.cameras; } const newDeviceList = this.apiClient.bootstrap?.cameras; // Notify the user about any new devices that we've discovered. if (newDeviceList) { for (const newDevice of newDeviceList) { // We only want to discover managed devices. if (!newDevice.isManaged) { continue; } const deviceName = Utils_1.default.getDeviceName(newDevice, newDevice.name, true); // We've discovered a new device. this.log.info("%s: Discovered %s: %s.", this.apiClient.nvrName, newDevice.modelKey, deviceName); } } this.cameras = newDeviceList; return newDeviceList; } async handleUpdate(event) { const updatePacket = UnifiApiUpdateUtil_1.ProtectApiUpdates.decodeUpdatePacket(this.log, event); if (!updatePacket) { this.log.error("%s: Unable to process message from the realtime update events API.", this.apiClient.nvrName); return; } const cameras = await this.getCameras(); // The update actions that we care about (doorbell rings, motion detection) look like this: // // action: "update" // id: "someCameraId" // modelKey: "camera" // newUpdateId: "ignorethis" // // The payloads are what differentiate them - one updates lastMotion and the other lastRing. switch (updatePacket.action.modelKey) { case "camera": { // We listen for the following camera update actions: // doorbell rings // motion detection // We're only interested in update actions. if (updatePacket.action.action !== "update") { return; } // Grab the right payload type, camera update payloads. const payload = updatePacket.payload; // Now filter out payloads we aren't interested in. We only want motion detection and doorbell rings for now. if (!payload.isMotionDetected && !payload.lastRing) { return; } // Get the camera that the event belongs to const camera = cameras?.find((x) => x.id == updatePacket.action.id); if (!camera) { return; } // It's a motion event - process it accordingly, // but only if we're not configured for smart motion events - we handle those elsewhere. if (payload.isMotionDetected) { if (payload.lastMotion) { this.log.debug(`Camera ${camera.name} detected a motion`); this.handleMotionEvent(camera, payload.lastMotion); } } // It's a ring event - process it accordingly. if (payload.lastRing) { this.log.debug(`Doorbell ${camera.name} rang`); this.handleDoorbellEvent(camera, payload.lastRing); } break; } case "event": { // We listen for the following event actions: // smart motion detection // We're only interested in add events. if (updatePacket.action.action !== "add") { return; } // Grab the right payload type, for event add payloads. const payload = updatePacket.payload; // We're only interested in smart motion detection events. if (payload.type !== "smartDetectZone") { return; } const camera = cameras?.find((x) => x.mac == updatePacket.action.id); if (!camera) { return; } // Handle smart motion events this.log.info(`Camera ${camera.name} detected a smart motion`); this.handleMotionEvent(camera, payload.start, payload.smartDetectTypes); return; } default: // It's not a modelKey we're interested in. We're done. return; } } handleMotionEvent(camera, lastMotion, detectedObjects = []) { // Have we seen this event before? If so...move along. if (this.lastMotion[camera.mac] >= lastMotion) { this.log.debug("%s: Skipping duplicate motion event.", camera.name); return; } // We only consider events that have happened within the last two refresh intervals. Otherwise, we assume // it's stale data and don't inform the user. if (Date.now() - lastMotion > this.config.refreshInterval * 2 * 1000) { this.log.debug("%s: Skipping motion event due to stale data.", camera.name); return; } // Remember this event. this.lastMotion[camera.mac] = lastMotion; // If we already have a motion event inflight, allow it to complete so we don't spam users. if (this.eventTimers[camera.mac]) { return; } // Call the external motion handler if specified if (this.config.onMotionEvent) { this.config.onMotionEvent(camera); } } handleDoorbellEvent(camera, lastRing) { // Have we seen this event before? If so...move along. It's unlikely we hit this in a doorbell scenario, but just in case. if (this.lastRing[camera.mac] >= lastRing) { this.log.debug("%s: Skipping duplicate doorbell ring.", camera.name); return; } // We only consider events that have happened within the last two refresh intervals. Otherwise, we assume it's stale // data and don't inform the user. if (Date.now() - lastRing > this.config.refreshInterval * 2 * 1000) { this.log.debug("%s: Skipping doorbell ring due to stale data.", camera.name); return; } // Remember this event. this.lastRing[camera.mac] = lastRing; // Call the external doorbell handler if specified if (this.config.onDoorbellEvent) { this.config.onDoorbellEvent(camera); } } } exports.default = UnifiCameraHandler; //# sourceMappingURL=UnifiCameraHandler.js.map