homebridge-unifi-protect 5.5.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +3 -3
  2. package/config.schema.json +17 -16
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +6 -6
  5. package/dist/index.js.map +1 -1
  6. package/dist/protect-camera.d.ts +58 -0
  7. package/dist/protect-camera.js +367 -246
  8. package/dist/protect-camera.js.map +1 -1
  9. package/dist/protect-device.d.ts +48 -0
  10. package/dist/protect-device.js +189 -0
  11. package/dist/protect-device.js.map +1 -0
  12. package/dist/protect-doorbell.d.ts +22 -0
  13. package/dist/protect-doorbell.js +75 -64
  14. package/dist/protect-doorbell.js.map +1 -1
  15. package/dist/protect-ffmpeg-record.d.ts +15 -0
  16. package/dist/protect-ffmpeg-record.js +48 -34
  17. package/dist/protect-ffmpeg-record.js.map +1 -1
  18. package/dist/protect-ffmpeg-stream.d.ts +15 -0
  19. package/dist/protect-ffmpeg-stream.js +22 -12
  20. package/dist/protect-ffmpeg-stream.js.map +1 -1
  21. package/dist/protect-ffmpeg.d.ts +42 -0
  22. package/dist/protect-ffmpeg.js +49 -58
  23. package/dist/protect-ffmpeg.js.map +1 -1
  24. package/dist/protect-light.d.ts +13 -0
  25. package/dist/protect-light.js +63 -40
  26. package/dist/protect-light.js.map +1 -1
  27. package/dist/protect-liveviews.d.ts +17 -0
  28. package/dist/protect-liveviews.js +117 -101
  29. package/dist/protect-liveviews.js.map +1 -1
  30. package/dist/protect-mqtt.d.ts +19 -0
  31. package/dist/protect-mqtt.js +26 -35
  32. package/dist/protect-mqtt.js.map +1 -1
  33. package/dist/protect-nvr-events.d.ts +30 -0
  34. package/dist/protect-nvr-events.js +168 -431
  35. package/dist/protect-nvr-events.js.map +1 -1
  36. package/dist/protect-nvr-systeminfo.d.ts +15 -0
  37. package/dist/protect-nvr-systeminfo.js +43 -49
  38. package/dist/protect-nvr-systeminfo.js.map +1 -1
  39. package/dist/protect-nvr.d.ts +48 -0
  40. package/dist/protect-nvr.js +327 -359
  41. package/dist/protect-nvr.js.map +1 -1
  42. package/dist/protect-options.d.ts +39 -0
  43. package/dist/protect-options.js +172 -6
  44. package/dist/protect-options.js.map +1 -1
  45. package/dist/protect-platform.d.ts +17 -0
  46. package/dist/protect-platform.js +17 -30
  47. package/dist/protect-platform.js.map +1 -1
  48. package/dist/protect-record.d.ts +33 -0
  49. package/dist/protect-record.js +130 -126
  50. package/dist/protect-record.js.map +1 -1
  51. package/dist/protect-rtp.d.ts +29 -0
  52. package/dist/protect-rtp.js +133 -16
  53. package/dist/protect-rtp.js.map +1 -1
  54. package/dist/protect-securitysystem.d.ts +18 -0
  55. package/dist/protect-securitysystem.js +105 -109
  56. package/dist/protect-securitysystem.js.map +1 -1
  57. package/dist/protect-sensor.d.ts +28 -0
  58. package/dist/protect-sensor.js +79 -97
  59. package/dist/protect-sensor.js.map +1 -1
  60. package/dist/protect-stream.d.ts +41 -0
  61. package/dist/protect-stream.js +298 -156
  62. package/dist/protect-stream.js.map +1 -1
  63. package/dist/protect-timeshift.d.ts +30 -0
  64. package/dist/protect-timeshift.js +65 -48
  65. package/dist/protect-timeshift.js.map +1 -1
  66. package/dist/protect-types.d.ts +50 -0
  67. package/dist/protect-types.js +22 -0
  68. package/dist/protect-types.js.map +1 -0
  69. package/dist/protect-viewer.d.ts +17 -0
  70. package/dist/protect-viewer.js +41 -47
  71. package/dist/protect-viewer.js.map +1 -1
  72. package/dist/settings.d.ts +22 -0
  73. package/dist/settings.js +30 -35
  74. package/dist/settings.js.map +1 -1
  75. package/homebridge-ui/public/index.html +715 -0
  76. package/homebridge-ui/server.js +156 -0
  77. package/package.json +15 -16
  78. package/dist/protect-accessory.js +0 -184
  79. package/dist/protect-accessory.js.map +0 -1
@@ -1,554 +1,291 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProtectNvrEvents = void 0;
4
- const unifi_protect_1 = require("unifi-protect");
5
- const protect_camera_1 = require("./protect-camera");
6
- const protect_accessory_1 = require("./protect-accessory");
7
- class ProtectNvrEvents {
1
+ import { ProtectReservedNames } from "./protect-types.js";
2
+ import { EventEmitter } from "node:events";
3
+ export class ProtectNvrEvents extends EventEmitter {
4
+ // Initialize an instance of our Protect events handler.
8
5
  constructor(nvr) {
6
+ super();
9
7
  this.api = nvr.platform.api;
10
- this.debug = nvr.platform.debug.bind(nvr.platform);
11
8
  this.eventTimers = {};
12
9
  this.hap = nvr.platform.api.hap;
13
10
  this.lastMotion = {};
14
11
  this.lastRing = {};
15
- this.log = nvr.platform.log;
16
- this.name = () => {
17
- return nvr.nvrApi.getNvrName();
18
- };
12
+ this.log = nvr.log;
19
13
  this.nvr = nvr;
20
- this.nvrApi = nvr.nvrApi;
14
+ this.ufpApi = nvr.ufpApi;
15
+ this.ufpDeviceState = {};
21
16
  this.motionDuration = nvr.platform.config.motionDuration;
22
17
  this.platform = nvr.platform;
23
18
  this.ringDuration = nvr.platform.config.ringDuration;
24
19
  this.unsupportedDevices = {};
25
- this.updatesListener = null;
20
+ this.eventsHandler = null;
21
+ this.ufpUpdatesHandler = null;
22
+ this.configureEvents();
26
23
  }
27
- // Check for event updates.
28
- update() {
29
- // Configure the updates API listener, if needed. This needs to be called
30
- // regularly because the connection to the update events websocket can be shutdown and reopened.
31
- return this.configureUpdatesListener();
32
- }
33
- // Configure the realtime system event API listener to trigger events on accessories, like motion.
34
- // This is now deprecated in favor of the realtime updates event API, which provides for more event types
35
- // than the realtime system events API.
36
- configureSystemEventListener() {
37
- // Only configure the event listener if it exists and it's not already configured.
38
- if (!this.nvrApi.eventsWs || this.updatesListener) {
39
- return true;
40
- }
41
- // Listen for any messages coming in from our listener.
42
- this.nvrApi.eventsWs.on("message", this.updatesListener = (event) => {
43
- var _a, _b;
44
- let nvrEvent;
45
- try {
46
- nvrEvent = JSON.parse(event.toString());
47
- }
48
- catch (error) {
49
- if (error instanceof SyntaxError) {
50
- this.log.error("%s: Unable to process message from the realtime system events API: \"%s\". Error: %s.", this.nvrApi.getNvrName(), event, error.message);
24
+ // Thanks to https://stackoverflow.com/a/48218209 for the foundation for this one.
25
+ // Merge Protect JSON update payloads into the Protect configuration JSON for a device while dealing with deep objects.
26
+ // @param {...object} objects - Objects to merge
27
+ // @returns {object} New object with merged key/values
28
+ patchUfpConfigJson(...objects) {
29
+ const isObject = (x) => (x && (typeof (x) === "object"));
30
+ return objects.reduce((prev, obj) => {
31
+ for (const key of Object.keys(obj)) {
32
+ const pVal = prev[key];
33
+ const oVal = obj[key];
34
+ if (Array.isArray(pVal) && Array.isArray(oVal)) {
35
+ prev[key] = oVal;
36
+ }
37
+ else if (isObject(pVal) && isObject(oVal)) {
38
+ prev[key] = this.patchUfpConfigJson(pVal, oVal);
51
39
  }
52
40
  else {
53
- this.log.error("%s: Unknown error has occurred: %s.", this.nvrApi.getNvrName(), error);
41
+ prev[key] = oVal;
54
42
  }
55
- // Errors mean that we're done now.
56
- return;
57
- }
58
- // We're interested in device state change events.
59
- if ((nvrEvent === null || nvrEvent === void 0 ? void 0 : nvrEvent.type) !== "DEVICE_STATE_CHANGED") {
60
- return;
61
- }
62
- // We only want Protect controllers.
63
- const controller = (_b = (_a = nvrEvent.apps) === null || _a === void 0 ? void 0 : _a.controllers) === null || _b === void 0 ? void 0 : _b.find(x => x.name === "protect");
64
- if (!controller) {
65
- return;
66
43
  }
67
- // Find the device in our list of accessories so we can fire off the motion event.
68
- const foundDevice = Object.keys(this.nvr.configuredDevices).find(x => this.nvr.configuredDevices[x].accessory.context.device.host === controller.info.lastMotionCameraAddress);
69
- // Nothing here - we may have disabled this device or it's associated NVR.
70
- if (!foundDevice) {
44
+ return prev;
45
+ }, {});
46
+ }
47
+ // Process Protect API update events.
48
+ ufpUpdates(packet) {
49
+ let protectDevice;
50
+ switch (packet.header.modelKey) {
51
+ case "nvr":
52
+ this.nvr.ufp = this.patchUfpConfigJson(this.nvr.ufp, packet.payload);
53
+ break;
54
+ default:
55
+ // Lookup the device.
56
+ protectDevice = this.nvr.deviceLookup(packet.header.id);
57
+ // Update our device state.
58
+ if (protectDevice) {
59
+ protectDevice.ufp = this.patchUfpConfigJson(protectDevice.ufp, packet.payload);
60
+ }
61
+ break;
62
+ }
63
+ // Update the internal list we maintain.
64
+ this.ufpDeviceState[packet.header.id] = Object.assign(this.ufpDeviceState[packet.header.id] ?? {}, packet.payload);
65
+ }
66
+ // Process device additions and removals from the Protect update events API.
67
+ manageDevices(packet) {
68
+ const payload = packet.payload;
69
+ // We only want adoption-related events.
70
+ if ((packet.header.modelKey !== "event") || ((payload.type !== "deviceAdopted") && (payload.type !== "deviceUnadopted"))) {
71
+ return;
72
+ }
73
+ // Make sure we have the right information to process the event.
74
+ if (!("deviceId" in payload.metadata) || !("text" in payload.metadata.deviceId)) {
75
+ return;
76
+ }
77
+ // Lookup the device.
78
+ const deviceId = payload.metadata.deviceId.text;
79
+ const protectDevice = this.nvr.deviceLookup(deviceId);
80
+ // We're adopting.
81
+ if (payload.type === "deviceAdopted") {
82
+ if (protectDevice) {
83
+ this.log.error("WE HAVE THE DEVICE ALREADY - WE ARE SCREWED!");
71
84
  return;
72
85
  }
73
- // Now grab the accessory associated with the Protect device.
74
- const accessory = this.nvr.configuredDevices[foundDevice].accessory;
75
- // If we don't have an accessory, it's probably because we've chosen to hide it. In that case,
76
- // just ignore and move on. Alternatively, it could be a new device that we just don't know about yet,
77
- // In either case, we keep ignore it.
78
- if (!accessory) {
86
+ this.nvr.addHomeKitDevice(this.ufpDeviceState[deviceId]);
87
+ return;
88
+ }
89
+ // We're unadopting.
90
+ if (payload.type === "deviceUnadopted") {
91
+ // If it's already gone, we're done.
92
+ if (!protectDevice) {
79
93
  return;
80
94
  }
81
- // The UniFi OS system events realtime API returns lastMotion in seconds rather than milliseconds.
82
- this.motionEventHandler(accessory, controller.info.lastMotion * 1000);
83
- });
84
- // Cleanup after ourselves.
85
- this.nvrApi.eventsWs.once("close", () => {
86
- var _a;
87
- if (this.updatesListener) {
88
- (_a = this.nvrApi.eventsWs) === null || _a === void 0 ? void 0 : _a.removeListener("message", this.updatesListener);
89
- this.updatesListener = null;
90
- }
91
- });
92
- return true;
95
+ // Remove the device.
96
+ this.nvr.removeHomeKitDevice(protectDevice);
97
+ return;
98
+ }
93
99
  }
94
- // Configure the realtime update events API listener to trigger events on accessories, like motion.
95
- configureUpdatesListener() {
100
+ // Listen to the UniFi Protect realtime updates API for updates we are interested in (e.g. motion).
101
+ configureEvents() {
96
102
  // Only configure the event listener if it exists and it's not already configured.
97
- if (!this.nvrApi.eventsWs || this.updatesListener) {
103
+ if (this.eventsHandler && this.ufpUpdatesHandler) {
98
104
  return true;
99
105
  }
100
- // Listen for any messages coming in from our listener.
101
- this.nvrApi.eventsWs.on("message", this.updatesListener = (event) => {
102
- var _a, _b, _c, _d;
103
- const updatePacket = unifi_protect_1.ProtectApiUpdates.decodeUpdatePacket(this.log, event);
104
- if (!updatePacket) {
105
- this.log.error("%s: Unable to process message from the realtime update events API.", this.nvrApi.getNvrName());
106
- return;
107
- }
108
- // The update actions that we care about (doorbell rings, motion detection) look like this:
109
- //
110
- // action: "update"
111
- // id: "someCameraId"
112
- // modelKey: "camera"
113
- // newUpdateId: "ignorethis"
114
- //
115
- // The payloads are what differentiate them - one updates lastMotion and the other lastRing.
116
- switch (updatePacket.action.modelKey) {
117
- case "camera": {
118
- // We listen for the following camera update actions:
119
- // doorbell LCD updates
120
- // doorbell rings
121
- // motion detection
122
- // We're only interested in update actions.
123
- if (updatePacket.action.action !== "update") {
124
- return;
125
- }
126
- // Grab the right payload type, camera update payloads.
127
- const payload = updatePacket.payload;
128
- // Now filter out payloads we aren't interested in. We only want motion detection and doorbell rings for now.
129
- if (!payload.isMotionDetected && !payload.lastRing && !payload.lcdMessage &&
130
- !payload.ledSettings && !payload.recordingSettings && !payload.state) {
131
- return;
132
- }
133
- // Lookup the accessory associated with this device.
134
- const accessory = this.nvr.accessoryLookup(updatePacket.action.id);
135
- // We don't know about this device - we're done.
136
- if (!accessory) {
137
- return;
138
- }
139
- // Update the device JSON on the accessory.
140
- accessory.context.device = Object.assign(accessory.context.device, payload);
141
- // Grab the device context.
142
- const device = accessory.context.device;
143
- // Lookup the ProtectCamera instance associated with this accessory.
144
- const protectCamera = this.nvr.configuredDevices[accessory.UUID];
145
- if (!protectCamera) {
146
- return;
147
- }
148
- // It's a motion event - process it accordingly.
149
- if (payload.isMotionDetected) {
150
- // We only want to process the motion event if we have the right payload, and either HKSV recording is enabled, or
151
- // HKSV recording is disabled and we have smart motion events disabled since We handle those elsewhere.
152
- if (payload.lastMotion &&
153
- (((_a = protectCamera.stream.hksv) === null || _a === void 0 ? void 0 : _a.isRecording) || (!((_b = protectCamera.stream.hksv) === null || _b === void 0 ? void 0 : _b.isRecording) && !protectCamera.smartDetectTypes.length)) &&
154
- this.nvr.optionEnabled(device, "Motion.NvrEvents", true)) {
155
- this.motionEventHandler(accessory, payload.lastMotion);
156
- }
157
- }
158
- // It's a ring event - process it accordingly.
159
- if (payload.lastRing && this.nvr.optionEnabled(device, "Doorbell.NvrEvents", true)) {
160
- this.doorbellEventHandler(accessory, payload.lastRing);
161
- }
162
- // It's a doorbell LCD message event - process it accordingly.
163
- if (payload.lcdMessage) {
164
- this.lcdMessageEventHandler(accessory, payload.lcdMessage);
165
- }
166
- // Process camera details updates:
167
- // - camera status light.
168
- // - camera recording settings.
169
- if ((payload.ledSettings && ("isEnabled" in payload.ledSettings)) ||
170
- (payload.recordingSettings && ("mode" in payload.recordingSettings)) || payload.recordingSettings) {
171
- this.cameraDetailsHandler(accessory, protectCamera);
172
- }
173
- break;
174
- }
175
- case "event": {
176
- // We listen for the following event actions:
177
- // smart motion detection
178
- // We're only interested in add events.
179
- if (updatePacket.action.action !== "add") {
180
- return;
181
- }
182
- // Grab the right payload type, for event add payloads.
183
- const payload = updatePacket.payload;
184
- // We're only interested in smart motion detection events.
185
- if (payload.type !== "smartDetectZone") {
186
- return;
187
- }
188
- // Lookup the accessory associated with this camera.
189
- const accessory = this.nvr.accessoryLookup(payload.camera);
190
- // We don't know about this camera - we're done.
191
- if (!accessory) {
192
- return;
193
- }
194
- // Grab the device context.
195
- const device = accessory.context.device;
196
- // Lookup the ProtectCamera instance associated with this accessory.
197
- const protectCamera = this.nvr.configuredDevices[accessory.UUID];
198
- if (!protectCamera) {
199
- return;
200
- }
201
- // Process the motion event.
202
- if (this.nvr.optionEnabled(device, "Motion.SmartDetect.NvrEvents", true)) {
203
- this.motionEventHandler(accessory, payload.start, payload.smartDetectTypes);
106
+ // Ensure we update our UFP state before we process any other events.
107
+ this.prependListener("updateEvent", this.ufpUpdatesHandler = this.ufpUpdates.bind(this));
108
+ // Process remove events.
109
+ this.prependListener("addEvent", this.manageDevices.bind(this));
110
+ // Listen for any messages coming in from our listener. We route events to the appropriate handlers based on the type of event that comes across.
111
+ this.ufpApi.on("message", this.eventsHandler = (packet) => {
112
+ switch (packet.header.action) {
113
+ case "add":
114
+ this.emit("addEvent", packet);
115
+ if (packet.payload.camera) {
116
+ this.emit("addEvent." + packet.payload.camera, packet);
204
117
  }
205
- return;
118
+ this.emit("addEvent." + packet.header.modelKey, packet);
206
119
  break;
207
- }
208
- case "light": {
209
- // We listen for the following light update actions:
210
- // light on / off
211
- // brightness adjustments
212
- // We're only interested in update actions.
213
- if (updatePacket.action.action !== "update") {
214
- return;
215
- }
216
- // Grab the right payload type, camera update payloads.
217
- const payload = updatePacket.payload;
218
- // Now filter out payloads we aren't interested in. We only want light state, brightness, and motion detection.
219
- if (!payload.isPirMotionDetected && !payload.isLightOn && !payload.lightDeviceSettings) {
220
- return;
221
- }
222
- // Lookup the accessory associated with this device.
223
- const accessory = this.nvr.accessoryLookup(updatePacket.action.id);
224
- // We don't know about this device - we're done.
225
- if (!accessory) {
226
- return;
227
- }
228
- // Grab the device context.
229
- const device = accessory.context.device;
230
- // Lookup the ProtectCamera instance associated with this accessory.
231
- const protectLight = this.nvr.configuredDevices[accessory.UUID];
232
- if (!protectLight) {
233
- return;
234
- }
235
- // It's a motion event - process it accordingly.
236
- if (payload.isPirMotionDetected && payload.lastMotion && this.nvr.optionEnabled(device, "Motion.NvrEvents", true)) {
237
- this.motionEventHandler(accessory, payload.lastMotion);
238
- }
239
- // It's a light power event - process it accordingly.
240
- if (payload.isLightOn) {
241
- this.lightPowerHandler(accessory, payload.isLightOn);
242
- }
243
- // It's light brightness event - process it accordingly.
244
- if ((_c = payload.lightDeviceSettings) === null || _c === void 0 ? void 0 : _c.ledLevel) {
245
- this.lightBrightnessHandler(accessory, payload.lightDeviceSettings.ledLevel);
246
- }
247
- break;
248
- }
249
- case "nvr": {
250
- // We listen for the following sensor update actions:
251
- // motion events
252
- // sensor enablement / configuration changes
253
- // sensor updates (humidity, light, temperature)
254
- // We're only interested in update actions.
255
- if (updatePacket.action.action !== "update") {
256
- return;
257
- }
258
- // Grab the right payload type.
259
- const payload = updatePacket.payload;
260
- // Now filter out payloads we aren't interested in. We only want NVR system information updates.
261
- if (!("systemInfo" in payload)) {
262
- return;
263
- }
264
- // Process it.
265
- (_d = this.nvr.systemInfo) === null || _d === void 0 ? void 0 : _d.updateDevice(false, payload.systemInfo);
120
+ case "remove":
121
+ this.emit("removeEvent", packet);
122
+ this.emit("removeEvent." + packet.header.id, packet);
123
+ this.emit("removeEvent." + packet.header.modelKey, packet);
266
124
  break;
267
- }
268
- case "sensor": {
269
- // We listen for the following sensor update actions:
270
- // motion events
271
- // sensor enablement / configuration changes
272
- // sensor updates (humidity, light, temperature)
273
- // We're only interested in update actions.
274
- if (updatePacket.action.action !== "update") {
275
- return;
276
- }
277
- // Grab the right payload type.
278
- const payload = updatePacket.payload;
279
- // Now filter out payloads we aren't interested in. We only want motion events, stats updates, and changes in sensor configuration.
280
- if (!("isMotionDetected" in payload) && !("isOpened" in payload) && !("stats" in payload) &&
281
- !("mountType" in payload) && !("alarmSettings" in payload) && !("humiditySettings" in payload) &&
282
- !("lightSettings" in payload) && !("motionSettings" in payload) && !("temperatureSettings" in payload) &&
283
- !("batteryStatus" in payload) && !("tamperingDetectedAt" in payload) && !("alarmTriggeredAt" in payload) &&
284
- !("state" in payload)) {
285
- return;
286
- }
287
- // Lookup the accessory associated with this device.
288
- const accessory = this.nvr.accessoryLookup(updatePacket.action.id);
289
- // We don't know about this device - we're done.
290
- if (!accessory) {
291
- return;
292
- }
293
- // Update the device JSON on the accessory.
294
- accessory.context.device = Object.assign(accessory.context.device, payload);
295
- // Grab the device context.
296
- const device = accessory.context.device;
297
- // Lookup the ProtectSensor instance associated with this accessory.
298
- const protectSensor = this.nvr.configuredDevices[accessory.UUID];
299
- if (!protectSensor) {
300
- return;
301
- }
302
- // It's a motion event - process it accordingly.
303
- if (payload.isMotionDetected && payload.motionDetectedAt && this.nvr.optionEnabled(device, "Motion.NvrEvents", true)) {
304
- this.motionEventHandler(accessory, payload.motionDetectedAt);
305
- }
306
- // Process it.
307
- this.sensorHandler(accessory, protectSensor);
125
+ case "update":
126
+ this.emit("updateEvent", packet);
127
+ this.emit("updateEvent." + packet.header.id, packet);
128
+ this.emit("updateEvent." + packet.header.modelKey, packet);
308
129
  break;
309
- }
310
130
  default:
311
- // It's not a modelKey we're interested in. We're done.
312
- return;
313
131
  break;
314
132
  }
315
133
  });
316
- // Cleanup after ourselves.
317
- this.nvrApi.eventsWs.once("close", () => {
318
- var _a;
319
- if (this.updatesListener) {
320
- (_a = this.nvrApi.eventsWs) === null || _a === void 0 ? void 0 : _a.removeListener("message", this.updatesListener);
321
- this.updatesListener = null;
322
- this.log.error("%s: UniFi Protect realtime events API has been closed. This is usually due to a controller restart or disconnect.", this.name());
323
- }
324
- });
325
134
  return true;
326
135
  }
327
- // Camera details event processing from UniFi Protect for state-specific information.
328
- cameraDetailsHandler(accessory, protectCamera) {
329
- // Update the camera details in HomeKit.
330
- protectCamera.updateDevice();
331
- }
332
136
  // Motion event processing from UniFi Protect.
333
- motionEventHandler(accessory, lastMotion, detectedObjects = []) {
334
- const device = accessory.context.device;
335
- if (!device || !lastMotion) {
137
+ motionEventHandler(protectDevice, lastMotion, detectedObjects = []) {
138
+ if (!protectDevice || !lastMotion) {
336
139
  return;
337
140
  }
338
141
  // Have we seen this event before? If so...move along.
339
- if (this.lastMotion[device.mac] >= lastMotion) {
340
- this.debug("%s: Skipping duplicate motion event.", this.nvrApi.getFullName(device));
341
- return;
342
- }
343
- // We only consider events that have happened within the last two refresh intervals. Otherwise, we assume
344
- // it's stale data and don't inform the user.
345
- if ((Date.now() - lastMotion) > (this.nvr.refreshInterval * 2 * 1000)) {
346
- this.debug("%s: Skipping motion event due to stale data.", this.nvrApi.getFullName(device));
142
+ if (this.lastMotion[protectDevice.ufp.mac] >= lastMotion) {
143
+ this.log.debug("Skipping duplicate motion event.");
347
144
  return;
348
145
  }
349
146
  // Remember this event.
350
- this.lastMotion[device.mac] = lastMotion;
147
+ this.lastMotion[protectDevice.ufp.mac] = lastMotion;
351
148
  // If we already have a motion event inflight, allow it to complete so we don't spam users.
352
- if (this.eventTimers[device.mac]) {
149
+ if (this.eventTimers[protectDevice.ufp.mac]) {
353
150
  return;
354
151
  }
355
152
  // Only notify the user if we have a motion sensor and it's active.
356
- const motionService = accessory.getService(this.hap.Service.MotionSensor);
153
+ const motionService = protectDevice.accessory.getService(this.hap.Service.MotionSensor);
357
154
  if (motionService) {
358
- this.motionEventDelivery(accessory, motionService, detectedObjects);
155
+ this.motionEventDelivery(protectDevice, motionService, detectedObjects);
359
156
  }
360
157
  }
361
158
  // Motion event delivery to HomeKit.
362
- motionEventDelivery(accessory, motionService, detectedObjects = []) {
363
- var _a, _b, _c, _d, _e;
364
- const device = accessory.context.device;
365
- // Lookup the ProtectCamera instance associated with this accessory.
366
- const protectCamera = this.nvr.configuredDevices[accessory.UUID];
367
- if (!protectCamera) {
159
+ motionEventDelivery(protectDevice, motionService, detectedObjects = []) {
160
+ if (!protectDevice) {
368
161
  return;
369
162
  }
370
163
  // If we have disabled motion events, we're done here.
371
- if (("detectMotion" in accessory.context) && !accessory.context.detectMotion) {
164
+ if (("detectMotion" in protectDevice.accessory.context) && !protectDevice.accessory.context.detectMotion) {
372
165
  return;
373
166
  }
374
- // Trigger the motion event if:
375
- // - It's not a smart motion event, or
376
- // - It's an HKSV event, or
377
- // - If HKSV is disabled and it's a smart motion event that we are interested in. Otherwise, we'll end up triggering multiple motion
378
- // events with HKSV enabled and smart motion detection enabled.
379
- if (!detectedObjects.length || ((_a = protectCamera.stream.hksv) === null || _a === void 0 ? void 0 : _a.isRecording) ||
380
- (!((_b = protectCamera.stream.hksv) === null || _b === void 0 ? void 0 : _b.isRecording) && detectedObjects.length && detectedObjects.filter(x => protectCamera.smartDetectTypes.includes(x)).length)) {
381
- // Trigger the motion event in HomeKit.
382
- motionService.updateCharacteristic(this.hap.Characteristic.MotionDetected, true);
383
- // Check to see if we have a motion trigger switch configured. If we do, update it.
384
- const triggerService = accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_MOTION_TRIGGER);
385
- if (triggerService) {
386
- triggerService.updateCharacteristic(this.hap.Characteristic.On, true);
387
- }
388
- // Publish the motion event to MQTT, if the user has configured it.
389
- (_c = this.nvr.mqtt) === null || _c === void 0 ? void 0 : _c.publish(accessory, "motion", "true");
390
- // Log the event, if configured to do so.
391
- if (this.nvr.optionEnabled(device, "Log.Motion", false)) {
392
- this.log.info("%s: Motion detected%s.", this.nvrApi.getFullName(device), ((protectCamera instanceof protect_camera_1.ProtectCamera) && !((_d = protectCamera.stream.hksv) === null || _d === void 0 ? void 0 : _d.isRecording) && detectedObjects.length) ? ": " + detectedObjects.join(", ") : "");
393
- }
167
+ const protectCamera = protectDevice;
168
+ // Trigger the motion event in HomeKit.
169
+ motionService.updateCharacteristic(this.hap.Characteristic.MotionDetected, true);
170
+ // Check to see if we have a motion trigger switch configured. If we do, update it.
171
+ const triggerService = protectDevice.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_MOTION_TRIGGER);
172
+ if (triggerService) {
173
+ triggerService.updateCharacteristic(this.hap.Characteristic.On, true);
174
+ }
175
+ // Publish the motion event to MQTT, if the user has configured it.
176
+ this.nvr.mqtt?.publish(protectDevice.accessory, "motion", "true");
177
+ // Log the event, if configured to do so.
178
+ if (protectDevice.hints.logMotion) {
179
+ protectDevice.log.info("Motion detected%s.", ((protectDevice.ufp.modelKey === "camera") && detectedObjects.length &&
180
+ (!protectCamera.stream.hksv?.isRecording || this.nvr.optionEnabled(protectDevice.ufp, "Motion.SmartDetect.ObjectSensors", false)))
181
+ ? ": " + detectedObjects.join(", ") : "");
394
182
  }
395
183
  // Trigger smart motion contact sensors, if configured.
396
184
  for (const detectedObject of detectedObjects) {
397
- const contactService = accessory.getServiceById(this.hap.Service.ContactSensor, protect_accessory_1.ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + detectedObject);
185
+ const contactService = protectDevice.accessory.getServiceById(this.hap.Service.ContactSensor, ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + detectedObject);
398
186
  if (contactService) {
399
187
  contactService.updateCharacteristic(this.hap.Characteristic.ContactSensorState, true);
400
188
  }
401
189
  // Publish the smart motion event to MQTT, if the user has configured it.
402
- (_e = this.nvr.mqtt) === null || _e === void 0 ? void 0 : _e.publish(accessory, "motion/smart/" + detectedObject, "true");
190
+ this.nvr.mqtt?.publish(protectDevice.accessory, "motion/smart/" + detectedObject, "true");
403
191
  }
404
192
  // Reset our motion event after motionDuration if we don't already have a reset timer inflight.
405
- if (!this.eventTimers[device.mac]) {
406
- this.eventTimers[device.mac] = setTimeout(() => {
407
- var _a;
408
- const thisMotionService = accessory.getService(this.hap.Service.MotionSensor);
193
+ if (!this.eventTimers[protectDevice.ufp.mac]) {
194
+ this.eventTimers[protectDevice.ufp.mac] = setTimeout(() => {
195
+ const thisMotionService = protectDevice.accessory.getService(this.hap.Service.MotionSensor);
409
196
  if (thisMotionService) {
410
197
  thisMotionService.updateCharacteristic(this.hap.Characteristic.MotionDetected, false);
411
198
  // Check to see if we have a motion trigger switch configured. If we do, update it.
412
- const thisTriggerService = accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_MOTION_TRIGGER);
199
+ const thisTriggerService = protectDevice.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_MOTION_TRIGGER);
413
200
  if (thisTriggerService) {
414
201
  thisTriggerService.updateCharacteristic(this.hap.Characteristic.On, false);
415
202
  }
416
- this.debug("%s: Resetting motion event.", this.nvrApi.getFullName(device));
203
+ this.log.debug("Resetting motion event.");
417
204
  }
418
205
  // Publish to MQTT, if the user has configured it.
419
- (_a = this.nvr.mqtt) === null || _a === void 0 ? void 0 : _a.publish(accessory, "motion", "false");
206
+ this.nvr.mqtt?.publish(protectDevice.accessory, "motion", "false");
420
207
  // Delete the timer from our motion event tracker.
421
- delete this.eventTimers[device.mac];
208
+ delete this.eventTimers[protectDevice.ufp.mac];
422
209
  }, this.motionDuration * 1000);
423
210
  }
424
211
  // Reset our smart motion contact sensors after motionDuration.
425
- if (!this.eventTimers[device.mac + ".Motion.SmartDetect.ObjectSensors"]) {
426
- this.eventTimers[device.mac + ".Motion.SmartDetect.ObjectSensors"] = setTimeout(() => {
427
- var _a;
212
+ if (!this.eventTimers[protectDevice.ufp.mac + ".Motion.SmartDetect.ObjectSensors"]) {
213
+ this.eventTimers[protectDevice.ufp.mac + ".Motion.SmartDetect.ObjectSensors"] = setTimeout(() => {
428
214
  // Reset smart motion contact sensors, if configured.
429
215
  for (const detectedObject of detectedObjects) {
430
- const contactService = accessory.getServiceById(this.hap.Service.ContactSensor, protect_accessory_1.ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + detectedObject);
216
+ const contactService = protectDevice.accessory.getServiceById(this.hap.Service.ContactSensor, ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + detectedObject);
431
217
  if (contactService) {
432
218
  contactService.updateCharacteristic(this.hap.Characteristic.ContactSensorState, false);
433
219
  }
434
220
  // Publish the smart motion event to MQTT, if the user has configured it.
435
- (_a = this.nvr.mqtt) === null || _a === void 0 ? void 0 : _a.publish(accessory, "motion/smart/" + detectedObject, "false");
436
- this.debug("%s: Resetting smart object motion event.", this.nvrApi.getFullName(device));
221
+ this.nvr.mqtt?.publish(protectDevice.accessory, "motion/smart/" + detectedObject, "false");
222
+ this.log.debug("Resetting smart object motion event.");
437
223
  }
438
224
  // Delete the timer from our motion event tracker.
439
- delete this.eventTimers[device.mac + ".Motion.SmartDetect.ObjectSensors"];
225
+ delete this.eventTimers[protectDevice.ufp.mac + ".Motion.SmartDetect.ObjectSensors"];
440
226
  }, this.motionDuration * 1000);
441
227
  }
442
228
  }
443
229
  // Doorbell event processing from UniFi Protect and delivered to HomeKit.
444
- doorbellEventHandler(accessory, lastRing) {
445
- var _a;
446
- const device = accessory.context.device;
447
- if (!device || !lastRing) {
230
+ doorbellEventHandler(protectDevice, lastRing) {
231
+ if (!protectDevice || !lastRing) {
448
232
  return;
449
233
  }
450
234
  // Have we seen this event before? If so...move along. It's unlikely we hit this in a doorbell scenario, but just in case.
451
- if (this.lastRing[device.mac] >= lastRing) {
452
- this.debug("%s: Skipping duplicate doorbell ring.", this.nvrApi.getFullName(device));
453
- return;
454
- }
455
- // We only consider events that have happened within the last two refresh intervals. Otherwise, we assume it's stale
456
- // data and don't inform the user.
457
- if ((Date.now() - lastRing) > (this.nvr.refreshInterval * 2 * 1000)) {
458
- this.debug("%s: Skipping doorbell ring due to stale data.", this.nvrApi.getFullName(device));
235
+ if (this.lastRing[protectDevice.ufp.mac] >= lastRing) {
236
+ this.log.debug("Skipping duplicate doorbell ring.");
459
237
  return;
460
238
  }
461
239
  // Remember this event.
462
- this.lastRing[device.mac] = lastRing;
240
+ this.lastRing[protectDevice.ufp.mac] = lastRing;
463
241
  // Only notify the user if we have a doorbell.
464
- const doorbellService = accessory.getService(this.hap.Service.Doorbell);
242
+ const doorbellService = protectDevice.accessory.getService(this.hap.Service.Doorbell);
465
243
  if (!doorbellService) {
466
244
  return;
467
245
  }
468
246
  // Trigger the doorbell. We delay this slightly to workaround what appears to be a race
469
247
  // condition bug in HomeKit. Inelegant, but effective.
470
248
  setTimeout(() => {
471
- var _a;
472
- (_a = doorbellService.getCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent)) === null || _a === void 0 ? void 0 : _a.sendEventNotification(this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
249
+ doorbellService.getCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent)
250
+ ?.sendEventNotification(this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
473
251
  }, 500);
474
252
  // Check to see if we have a doorbell trigger switch configured. If we do, update it.
475
- const triggerService = accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
253
+ const triggerService = protectDevice.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
476
254
  if (triggerService) {
477
255
  // Kill any inflight trigger reset.
478
- if (this.eventTimers[device.mac + ".Doorbell.Ring.Trigger"]) {
479
- clearTimeout(this.eventTimers[device.mac + ".Doorbell.Ring.Trigger"]);
480
- delete this.eventTimers[device.mac + ".Doorbell.Ring.Trigger"];
256
+ if (this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring.Trigger"]) {
257
+ clearTimeout(this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring.Trigger"]);
258
+ delete this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring.Trigger"];
481
259
  }
482
- const protectCamera = this.nvr.configuredDevices[accessory.UUID];
483
260
  // Flag that we're ringing.
484
- if (protectCamera) {
485
- protectCamera.isRinging = true;
486
- }
261
+ protectDevice.isRinging = true;
487
262
  // Update the trigger switch state.
488
263
  triggerService.updateCharacteristic(this.hap.Characteristic.On, true);
489
264
  // Reset our doorbell trigger after ringDuration.
490
- this.eventTimers[device.mac + ".Doorbell.Ring.Trigger"] = setTimeout(() => {
491
- if (protectCamera) {
492
- protectCamera.isRinging = false;
493
- }
265
+ this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring.Trigger"] = setTimeout(() => {
266
+ protectDevice.isRinging = false;
494
267
  triggerService.updateCharacteristic(this.hap.Characteristic.On, false);
495
- this.debug("%s: Resetting doorbell ring trigger.", this.nvrApi.getFullName(device));
268
+ this.log.debug("Resetting doorbell ring trigger.");
496
269
  // Delete the timer from our motion event tracker.
497
- delete this.eventTimers[device.mac + ".Doorbell.Ring.Trigger"];
270
+ delete this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring.Trigger"];
498
271
  }, this.ringDuration * 1000);
499
272
  }
500
273
  // Publish to MQTT, if the user has configured it.
501
- (_a = this.nvr.mqtt) === null || _a === void 0 ? void 0 : _a.publish(accessory, "doorbell", "true");
502
- if (this.nvr.optionEnabled(device, "Log.Doorbell", false)) {
503
- this.log.info("%s: Doorbell ring detected.", this.nvrApi.getFullName(device));
274
+ this.nvr.mqtt?.publish(protectDevice.accessory, "doorbell", "true");
275
+ if (protectDevice.hints.logDoorbell) {
276
+ protectDevice.log.info("Doorbell ring detected.");
504
277
  }
505
278
  // Kill any inflight ring reset.
506
- if (this.eventTimers[device.mac + ".Doorbell.Ring"]) {
507
- clearTimeout(this.eventTimers[device.mac + ".Doorbell.Ring"]);
508
- delete this.eventTimers[device.mac + ".Doorbell.Ring"];
279
+ if (this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring"]) {
280
+ clearTimeout(this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring"]);
281
+ delete this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring"];
509
282
  }
510
283
  // Fire off our MQTT doorbell ring event after ringDuration.
511
- this.eventTimers[device.mac + ".Doorbell.Ring"] = setTimeout(() => {
512
- var _a;
513
- (_a = this.nvr.mqtt) === null || _a === void 0 ? void 0 : _a.publish(accessory, "doorbell", "false");
284
+ this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring"] = setTimeout(() => {
285
+ this.nvr.mqtt?.publish(protectDevice.accessory, "doorbell", "false");
514
286
  // Delete the timer from our event tracker.
515
- delete this.eventTimers[device.mac + ".Doorbell.Ring"];
287
+ delete this.eventTimers[protectDevice.ufp.mac + ".Doorbell.Ring"];
516
288
  }, this.ringDuration * 1000);
517
289
  }
518
- // LCD message event processing from UniFi Protect and delivered to HomeKit.
519
- lcdMessageEventHandler(accessory, lcdMessage) {
520
- var _a;
521
- const device = accessory.context.device;
522
- if (!device) {
523
- return;
524
- }
525
- (_a = this.nvr.configuredDevices[accessory.UUID]) === null || _a === void 0 ? void 0 : _a.updateLcdSwitch(lcdMessage);
526
- }
527
- // Light power state event processing from UniFi Protect.
528
- lightPowerHandler(accessory, lightState) {
529
- const device = accessory.context.device;
530
- if (!device) {
531
- return;
532
- }
533
- // Update the power state on the accessory.
534
- const lightService = accessory.getService(this.hap.Service.Lightbulb);
535
- lightService === null || lightService === void 0 ? void 0 : lightService.updateCharacteristic(this.hap.Characteristic.On, lightState);
536
- }
537
- // Light power state event processing from UniFi Protect.
538
- lightBrightnessHandler(accessory, brightness) {
539
- const device = accessory.context.device;
540
- if (!device || (brightness < 1)) {
541
- return;
542
- }
543
- // Update the power state on the accessory.
544
- const lightService = accessory.getService(this.hap.Service.Lightbulb);
545
- lightService === null || lightService === void 0 ? void 0 : lightService.updateCharacteristic(this.hap.Characteristic.Brightness, (brightness - 1) * 20);
546
- }
547
- // Sensor state event processing from UniFi Protect.
548
- sensorHandler(accessory, protectSensor) {
549
- // Update the sensor state in HomeKit.
550
- protectSensor.updateDevice();
551
- }
552
290
  }
553
- exports.ProtectNvrEvents = ProtectNvrEvents;
554
291
  //# sourceMappingURL=protect-nvr-events.js.map