homebridge-unifi-protect 6.18.1 → 6.20.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.
- package/dist/devices/index.d.ts +11 -0
- package/dist/devices/index.js +16 -0
- package/dist/devices/index.js.map +1 -0
- package/dist/{protect-camera-package.js → devices/protect-camera-package.js} +2 -4
- package/dist/devices/protect-camera-package.js.map +1 -0
- package/dist/{protect-camera.d.ts → devices/protect-camera.d.ts} +2 -5
- package/dist/{protect-camera.js → devices/protect-camera.js} +119 -210
- package/dist/devices/protect-camera.js.map +1 -0
- package/dist/{protect-chime.d.ts → devices/protect-chime.d.ts} +4 -1
- package/dist/devices/protect-chime.js +180 -0
- package/dist/devices/protect-chime.js.map +1 -0
- package/dist/{protect-device.d.ts → devices/protect-device.d.ts} +12 -5
- package/dist/{protect-device.js → devices/protect-device.js} +159 -109
- package/dist/devices/protect-device.js.map +1 -0
- package/dist/{protect-doorbell.d.ts → devices/protect-doorbell.d.ts} +1 -1
- package/dist/{protect-doorbell.js → devices/protect-doorbell.js} +50 -83
- package/dist/devices/protect-doorbell.js.map +1 -0
- package/dist/{protect-light.d.ts → devices/protect-light.d.ts} +1 -1
- package/dist/{protect-light.js → devices/protect-light.js} +34 -44
- package/dist/devices/protect-light.js.map +1 -0
- package/dist/{protect-liveviews.d.ts → devices/protect-liveviews.d.ts} +1 -1
- package/dist/{protect-liveviews.js → devices/protect-liveviews.js} +11 -21
- package/dist/devices/protect-liveviews.js.map +1 -0
- package/dist/{protect-nvr-systeminfo.d.ts → devices/protect-nvr-systeminfo.d.ts} +1 -1
- package/dist/{protect-nvr-systeminfo.js → devices/protect-nvr-systeminfo.js} +3 -9
- package/dist/devices/protect-nvr-systeminfo.js.map +1 -0
- package/dist/{protect-securitysystem.d.ts → devices/protect-securitysystem.d.ts} +2 -3
- package/dist/{protect-securitysystem.js → devices/protect-securitysystem.js} +32 -59
- package/dist/devices/protect-securitysystem.js.map +1 -0
- package/dist/{protect-sensor.d.ts → devices/protect-sensor.d.ts} +3 -2
- package/dist/devices/protect-sensor.js +402 -0
- package/dist/devices/protect-sensor.js.map +1 -0
- package/dist/{protect-viewer.d.ts → devices/protect-viewer.d.ts} +1 -1
- package/dist/{protect-viewer.js → devices/protect-viewer.js} +21 -28
- package/dist/devices/protect-viewer.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +6 -0
- package/dist/ffmpeg/index.js +11 -0
- package/dist/ffmpeg/index.js.map +1 -0
- package/dist/{protect-ffmpeg-codecs.d.ts → ffmpeg/protect-ffmpeg-codecs.d.ts} +1 -1
- package/dist/ffmpeg/protect-ffmpeg-codecs.js.map +1 -0
- package/dist/{protect-ffmpeg-exec.d.ts → ffmpeg/protect-ffmpeg-exec.d.ts} +5 -2
- package/dist/{protect-ffmpeg-exec.js → ffmpeg/protect-ffmpeg-exec.js} +13 -1
- package/dist/ffmpeg/protect-ffmpeg-exec.js.map +1 -0
- package/dist/{protect-ffmpeg-options.d.ts → ffmpeg/protect-ffmpeg-options.d.ts} +1 -1
- package/dist/{protect-ffmpeg-options.js → ffmpeg/protect-ffmpeg-options.js} +1 -1
- package/dist/ffmpeg/protect-ffmpeg-options.js.map +1 -0
- package/dist/{protect-ffmpeg-record.d.ts → ffmpeg/protect-ffmpeg-record.d.ts} +1 -1
- package/dist/{protect-ffmpeg-record.js → ffmpeg/protect-ffmpeg-record.js} +7 -3
- package/dist/ffmpeg/protect-ffmpeg-record.js.map +1 -0
- package/dist/{protect-ffmpeg-stream.d.ts → ffmpeg/protect-ffmpeg-stream.d.ts} +1 -1
- package/dist/ffmpeg/protect-ffmpeg-stream.js.map +1 -0
- package/dist/{protect-ffmpeg.d.ts → ffmpeg/protect-ffmpeg.d.ts} +3 -3
- package/dist/ffmpeg/protect-ffmpeg.js.map +1 -0
- package/dist/{protect-nvr-events.d.ts → protect-events.d.ts} +3 -4
- package/dist/{protect-nvr-events.js → protect-events.js} +4 -4
- package/dist/protect-events.js.map +1 -0
- package/dist/protect-mqtt.d.ts +3 -2
- package/dist/protect-mqtt.js +30 -6
- package/dist/protect-mqtt.js.map +1 -1
- package/dist/protect-nvr.d.ts +4 -8
- package/dist/protect-nvr.js +82 -129
- package/dist/protect-nvr.js.map +1 -1
- package/dist/protect-options.js +7 -5
- package/dist/protect-options.js.map +1 -1
- package/dist/protect-platform.d.ts +1 -1
- package/dist/protect-platform.js +1 -1
- package/dist/protect-platform.js.map +1 -1
- package/dist/protect-record.d.ts +4 -2
- package/dist/protect-record.js +37 -34
- package/dist/protect-record.js.map +1 -1
- package/dist/protect-snapshot.d.ts +21 -0
- package/dist/protect-snapshot.js +200 -0
- package/dist/protect-snapshot.js.map +1 -0
- package/dist/protect-stream.d.ts +3 -7
- package/dist/protect-stream.js +7 -86
- package/dist/protect-stream.js.map +1 -1
- package/dist/protect-timeshift.d.ts +3 -2
- package/dist/protect-timeshift.js +31 -30
- package/dist/protect-timeshift.js.map +1 -1
- package/dist/protect-types.d.ts +5 -6
- package/dist/protect-types.js +8 -0
- package/dist/protect-types.js.map +1 -1
- package/dist/settings.d.ts +3 -6
- package/dist/settings.js +6 -12
- package/dist/settings.js.map +1 -1
- package/homebridge-ui/public/index.html +2 -0
- package/package.json +12 -12
- package/dist/protect-camera-package.js.map +0 -1
- package/dist/protect-camera.js.map +0 -1
- package/dist/protect-chime.js +0 -108
- package/dist/protect-chime.js.map +0 -1
- package/dist/protect-device.js.map +0 -1
- package/dist/protect-doorbell.js.map +0 -1
- package/dist/protect-ffmpeg-codecs.js.map +0 -1
- package/dist/protect-ffmpeg-exec.js.map +0 -1
- package/dist/protect-ffmpeg-options.js.map +0 -1
- package/dist/protect-ffmpeg-record.js.map +0 -1
- package/dist/protect-ffmpeg-stream.js.map +0 -1
- package/dist/protect-ffmpeg.js.map +0 -1
- package/dist/protect-light.js.map +0 -1
- package/dist/protect-liveviews.js.map +0 -1
- package/dist/protect-nvr-events.js.map +0 -1
- package/dist/protect-nvr-systeminfo.js.map +0 -1
- package/dist/protect-securitysystem.js.map +0 -1
- package/dist/protect-sensor.js +0 -449
- package/dist/protect-sensor.js.map +0 -1
- package/dist/protect-viewer.js.map +0 -1
- /package/dist/{protect-camera-package.d.ts → devices/protect-camera-package.d.ts} +0 -0
- /package/dist/{protect-ffmpeg-codecs.js → ffmpeg/protect-ffmpeg-codecs.js} +0 -0
- /package/dist/{protect-ffmpeg-stream.js → ffmpeg/protect-ffmpeg-stream.js} +0 -0
- /package/dist/{protect-ffmpeg.js → ffmpeg/protect-ffmpeg.js} +0 -0
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProtectReservedNames, toCamelCase } from "../protect-types.js";
|
|
2
|
+
import { PROTECT_HOMEKIT_IDR_INTERVAL } from "../settings.js";
|
|
2
3
|
import { ProtectDevice } from "./protect-device.js";
|
|
3
|
-
import {
|
|
4
|
-
import { ProtectStreamingDelegate } from "./protect-stream.js";
|
|
4
|
+
import { ProtectStreamingDelegate } from "../protect-stream.js";
|
|
5
5
|
export class ProtectCamera extends ProtectDevice {
|
|
6
6
|
hasHksv;
|
|
7
7
|
isDeleted;
|
|
8
|
-
isDoorbellConfigured;
|
|
9
8
|
isRinging;
|
|
10
|
-
isVideoConfigured;
|
|
11
9
|
detectLicensePlate;
|
|
12
10
|
rtspEntries;
|
|
13
11
|
rtspQuality;
|
|
@@ -16,11 +14,9 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
16
14
|
// Create an instance.
|
|
17
15
|
constructor(nvr, device, accessory) {
|
|
18
16
|
super(nvr, accessory);
|
|
19
|
-
this.isDoorbellConfigured = false;
|
|
20
17
|
this.hasHksv = false;
|
|
21
18
|
this.isDeleted = false;
|
|
22
19
|
this.isRinging = false;
|
|
23
|
-
this.isVideoConfigured = false;
|
|
24
20
|
this.detectLicensePlate = [];
|
|
25
21
|
this.rtspEntries = [];
|
|
26
22
|
this.rtspQuality = {};
|
|
@@ -35,6 +31,7 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
35
31
|
this.hints.crop = this.hasFeature("Video.Crop");
|
|
36
32
|
this.hints.hardwareDecoding = true;
|
|
37
33
|
this.hints.hardwareTranscoding = this.hasFeature("Video.Transcode.Hardware");
|
|
34
|
+
this.hints.highResSnapshots = this.hasFeature("Video.HighResSnapshots");
|
|
38
35
|
this.hints.ledStatus = this.ufp.featureFlags.hasLedStatus && this.hasFeature("Device.StatusLed");
|
|
39
36
|
this.hints.logDoorbell = this.hasFeature("Log.Doorbell");
|
|
40
37
|
this.hints.logHksv = this.hasFeature("Log.HKSV");
|
|
@@ -48,29 +45,13 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
48
45
|
}
|
|
49
46
|
// Configure a camera accessory for HomeKit.
|
|
50
47
|
async configureDevice() {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
// Save the motion detection switch state before we wipeout the context.
|
|
54
|
-
if ("detectMotion" in this.accessory.context) {
|
|
55
|
-
detectMotion = this.accessory.context.detectMotion;
|
|
56
|
-
}
|
|
57
|
-
// Default to disabling the dynamic bitrate setting.
|
|
58
|
-
let dynamicBitrate = false;
|
|
59
|
-
// Save the dynamic bitrate switch state before we wipeout the context.
|
|
60
|
-
if ("dynamicBitrate" in this.accessory.context) {
|
|
61
|
-
dynamicBitrate = this.accessory.context.dynamicBitrate;
|
|
62
|
-
}
|
|
63
|
-
// Default to enabling HKSV recording.
|
|
64
|
-
let hksvRecording = true;
|
|
65
|
-
// Save the HKSV recording switch state before we wipeout the context.
|
|
66
|
-
if ("hksvRecording" in this.accessory.context) {
|
|
67
|
-
hksvRecording = this.accessory.context.hksvRecording;
|
|
68
|
-
}
|
|
48
|
+
// Save our context for reference before we recreate it.
|
|
49
|
+
const savedContext = this.accessory.context;
|
|
69
50
|
// Clean out the context object in case it's been polluted somehow.
|
|
70
51
|
this.accessory.context = {};
|
|
71
|
-
this.accessory.context.detectMotion = detectMotion;
|
|
72
|
-
this.accessory.context.dynamicBitrate = dynamicBitrate;
|
|
73
|
-
this.accessory.context.hksvRecording = hksvRecording;
|
|
52
|
+
this.accessory.context.detectMotion = savedContext.detectMotion ?? true;
|
|
53
|
+
this.accessory.context.dynamicBitrate = savedContext.dynamicBitrate ?? false;
|
|
54
|
+
this.accessory.context.hksvRecording = savedContext.hksvRecording ?? true;
|
|
74
55
|
this.accessory.context.mac = this.ufp.mac;
|
|
75
56
|
this.accessory.context.nvr = this.nvr.ufp.mac;
|
|
76
57
|
// Inform the user that motion detection will suck.
|
|
@@ -108,8 +89,6 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
108
89
|
this.configureHksvRecordingSwitch();
|
|
109
90
|
// Configure our video stream.
|
|
110
91
|
await this.configureVideoStream();
|
|
111
|
-
// Configure our snapshot updates.
|
|
112
|
-
void this.configureSnapshotUpdates();
|
|
113
92
|
// Configure our camera details.
|
|
114
93
|
this.configureCameraDetails();
|
|
115
94
|
// Configure our bitrate switch.
|
|
@@ -147,7 +126,7 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
147
126
|
// smart motion events disabled (or a device without smart motion capabilities) since those are handled elsewhere.
|
|
148
127
|
if (this.stream?.hksv?.isRecording || (!this.stream?.hksv?.isRecording &&
|
|
149
128
|
(!this.ufp.featureFlags.smartDetectTypes.length || (this.ufp.featureFlags.smartDetectTypes.length && !this.hints.smartDetect)))) {
|
|
150
|
-
this.nvr.events.motionEventHandler(this
|
|
129
|
+
this.nvr.events.motionEventHandler(this);
|
|
151
130
|
}
|
|
152
131
|
}
|
|
153
132
|
// Process ring events.
|
|
@@ -172,7 +151,7 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
172
151
|
return;
|
|
173
152
|
}
|
|
174
153
|
// Process the motion event.
|
|
175
|
-
this.nvr.events.motionEventHandler(this, payload.
|
|
154
|
+
this.nvr.events.motionEventHandler(this, payload.smartDetectTypes, ("metadata" in payload) ? payload.metadata : undefined);
|
|
176
155
|
}
|
|
177
156
|
// Configure discrete smart motion contact sensors for HomeKit.
|
|
178
157
|
configureMotionSmartSensor() {
|
|
@@ -204,30 +183,22 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
204
183
|
}
|
|
205
184
|
// A utility for us to add contact sensors.
|
|
206
185
|
const addSmartDetectContactSensor = (name, serviceId, errorMessage) => {
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
if (!
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (!contactService) {
|
|
214
|
-
this.log.error(errorMessage);
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
contactService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
218
|
-
// Finally, add it to the camera.
|
|
219
|
-
this.accessory.addService(contactService);
|
|
186
|
+
// Acquire the service.
|
|
187
|
+
const service = this.acquireService(this.hap.Service.ContactSensor, name, serviceId);
|
|
188
|
+
// Fail gracefully.
|
|
189
|
+
if (!service) {
|
|
190
|
+
this.log.error(errorMessage);
|
|
191
|
+
return false;
|
|
220
192
|
}
|
|
221
193
|
// Initialize the sensor.
|
|
222
|
-
|
|
223
|
-
contactService.updateCharacteristic(this.hap.Characteristic.ContactSensorState, false);
|
|
194
|
+
service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, false);
|
|
224
195
|
return true;
|
|
225
196
|
};
|
|
226
197
|
let enabledContactSensors = [];
|
|
227
198
|
// Add individual contact sensors for each object detection type, if needed.
|
|
228
199
|
if (this.hasFeature("Motion.SmartDetect.ObjectSensors")) {
|
|
229
200
|
for (const smartDetectType of this.ufp.featureFlags.smartDetectTypes) {
|
|
230
|
-
if (addSmartDetectContactSensor(this.accessoryName + " " +
|
|
201
|
+
if (addSmartDetectContactSensor(this.accessoryName + " " + toCamelCase(smartDetectType), ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + smartDetectType, "Unable to add smart motion contact sensor for " + smartDetectType + " detection.")) {
|
|
231
202
|
enabledContactSensors.push(smartDetectType);
|
|
232
203
|
}
|
|
233
204
|
}
|
|
@@ -250,17 +221,17 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
250
221
|
}
|
|
251
222
|
// Configure a switch to manually trigger a doorbell ring event for HomeKit.
|
|
252
223
|
configureDoorbellTrigger() {
|
|
253
|
-
// Find the switch service, if it exists.
|
|
254
|
-
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
|
|
255
224
|
// See if we have a doorbell service configured.
|
|
256
225
|
let doorbellService = this.accessory.getService(this.hap.Service.Doorbell);
|
|
257
|
-
//
|
|
258
|
-
if (!this.
|
|
259
|
-
|
|
260
|
-
|
|
226
|
+
// Validate whether we should have this service enabled.
|
|
227
|
+
if (!this.validService(this.hap.Service.Switch, () => {
|
|
228
|
+
// Doorbell switches are disabled by default and primarily exist for automation purposes.
|
|
229
|
+
if (!this.hasFeature("Doorbell.Trigger")) {
|
|
230
|
+
return false;
|
|
261
231
|
}
|
|
262
|
-
|
|
263
|
-
|
|
232
|
+
return true;
|
|
233
|
+
}, ProtectReservedNames.SWITCH_DOORBELL_TRIGGER)) {
|
|
234
|
+
// Since we aren't enabling the doorbell trigger on this camera, remove the doorbell service if the camera isn't actually doorbell-capable hardware.
|
|
264
235
|
if (!this.ufp.featureFlags.isDoorbell && doorbellService) {
|
|
265
236
|
this.accessory.removeService(doorbellService);
|
|
266
237
|
}
|
|
@@ -278,16 +249,12 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
278
249
|
return false;
|
|
279
250
|
}
|
|
280
251
|
}
|
|
281
|
-
const triggerName = this.accessoryName + " Doorbell Trigger";
|
|
282
252
|
// Add the switch to the camera, if needed.
|
|
253
|
+
const triggerService = this.acquireService(this.hap.Service.Switch, this.accessoryName + " Doorbell Trigger", ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
|
|
254
|
+
// Fail gracefully.
|
|
283
255
|
if (!triggerService) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
this.log.error("Unable to add the doorbell trigger.");
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
triggerService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
290
|
-
this.accessory.addService(triggerService);
|
|
256
|
+
this.log.error("Unable to add the doorbell trigger.");
|
|
257
|
+
return false;
|
|
291
258
|
}
|
|
292
259
|
// Trigger the doorbell.
|
|
293
260
|
triggerService.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
|
|
@@ -309,31 +276,21 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
309
276
|
}
|
|
310
277
|
});
|
|
311
278
|
// Initialize the switch.
|
|
312
|
-
triggerService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, triggerName);
|
|
313
279
|
triggerService.updateCharacteristic(this.hap.Characteristic.On, false);
|
|
314
280
|
this.log.info("Enabling doorbell automation trigger.");
|
|
315
281
|
return true;
|
|
316
282
|
}
|
|
317
283
|
// Configure the doorbell service for HomeKit.
|
|
318
284
|
configureVideoDoorbell() {
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// Add the doorbell service to this Protect doorbell. HomeKit requires the doorbell service to be
|
|
326
|
-
// marked as the primary service on the accessory.
|
|
327
|
-
if (!doorbellService) {
|
|
328
|
-
doorbellService = new this.hap.Service.Doorbell(this.accessoryName);
|
|
329
|
-
if (!doorbellService) {
|
|
330
|
-
this.log.error("Unable to add doorbell.");
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
this.accessory.addService(doorbellService);
|
|
285
|
+
// Acquire the service.
|
|
286
|
+
const service = this.acquireService(this.hap.Service.Doorbell);
|
|
287
|
+
// Fail gracefully.
|
|
288
|
+
if (!service) {
|
|
289
|
+
this.log.error("Unable to add doorbell.");
|
|
290
|
+
return false;
|
|
334
291
|
}
|
|
335
|
-
|
|
336
|
-
|
|
292
|
+
// Add the doorbell service to this Protect doorbell. HomeKit requires the doorbell service to be marked as the primary service on the accessory.
|
|
293
|
+
service.setPrimaryService(true);
|
|
337
294
|
return true;
|
|
338
295
|
}
|
|
339
296
|
// Configure additional camera-specific characteristics for HomeKit.
|
|
@@ -416,18 +373,15 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
416
373
|
// Figure out which camera channels are RTSP-enabled, and user-enabled.
|
|
417
374
|
let cameraChannels = this.ufp.channels.filter(x => x.isRtspEnabled && this.hasFeature("Video.Stream." + x.name, true));
|
|
418
375
|
// Make sure we've got a HomeKit compatible IDR frame interval. If not, let's take care of that.
|
|
419
|
-
|
|
376
|
+
const idrChannels = cameraChannels.filter(x => x.idrInterval !== PROTECT_HOMEKIT_IDR_INTERVAL);
|
|
420
377
|
if (idrChannels.length) {
|
|
421
|
-
// Edit the channel map.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return x;
|
|
425
|
-
});
|
|
426
|
-
this.ufp = await this.nvr.ufpApi.updateDevice(this.ufp, { channels: idrChannels }) ?? this.ufp;
|
|
378
|
+
// Edit the channel map and update the Protect controller.
|
|
379
|
+
this.ufp = await this.nvr.ufpApi.updateDevice(this.ufp, { channels: idrChannels.map(x => Object.assign(x, { idrInterval: PROTECT_HOMEKIT_IDR_INTERVAL })) }) ??
|
|
380
|
+
this.ufp;
|
|
427
381
|
}
|
|
428
382
|
// Set the camera and shapshot URLs.
|
|
429
|
-
const cameraUrl = "rtsps://" + (this.nvr.
|
|
430
|
-
// Filter out any package camera entries.
|
|
383
|
+
const cameraUrl = "rtsps://" + (this.nvr.config.overrideAddress ?? this.ufp.connectionHost) + ":" + this.nvr.ufp.ports.rtsps.toString() + "/";
|
|
384
|
+
// Filter out any package camera entries. We deal with those independently in the package camera class.
|
|
431
385
|
cameraChannels = cameraChannels.filter(x => x.name !== "Package Camera");
|
|
432
386
|
// No RTSP streams are available that meet our criteria - we're done.
|
|
433
387
|
if (!cameraChannels.length) {
|
|
@@ -518,8 +472,8 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
518
472
|
}
|
|
519
473
|
// Publish our updated list of supported resolutions and their URLs.
|
|
520
474
|
this.rtspEntries = rtspEntries;
|
|
521
|
-
// If we'
|
|
522
|
-
if (this.
|
|
475
|
+
// If we've already configured the HomeKit video streaming delegate, we're done here.
|
|
476
|
+
if (this.stream) {
|
|
523
477
|
return true;
|
|
524
478
|
}
|
|
525
479
|
// Inform users about our RTSP entry mapping, if we're debugging.
|
|
@@ -541,31 +495,20 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
541
495
|
}
|
|
542
496
|
// Inform the user if we've set a streaming default.
|
|
543
497
|
if (this.rtspQuality.StreamingDefault) {
|
|
544
|
-
this.log.info("Video streaming configured to use only: %s.",
|
|
498
|
+
this.log.info("Video streaming configured to use only: %s.", toCamelCase(this.rtspQuality.StreamingDefault));
|
|
499
|
+
}
|
|
500
|
+
// Inform the user if they've selected the legacy snapshot API.
|
|
501
|
+
if (!this.hints.highResSnapshots) {
|
|
502
|
+
this.log.info("Disabling the use of higher quality snapshots.");
|
|
545
503
|
}
|
|
546
504
|
// Inform the user if we've set a recording default.
|
|
547
505
|
if (this.rtspQuality.RecordingDefault) {
|
|
548
|
-
this.log.info("HomeKit Secure Video event recording configured to use only: %s.",
|
|
506
|
+
this.log.info("HomeKit Secure Video event recording configured to use only: %s.", toCamelCase(this.rtspQuality.RecordingDefault));
|
|
549
507
|
}
|
|
550
508
|
// Configure the video stream with our resolutions.
|
|
551
509
|
this.stream = new ProtectStreamingDelegate(this, this.rtspEntries.map(x => x.resolution));
|
|
552
510
|
// Fire up the controller and inform HomeKit about it.
|
|
553
511
|
this.accessory.configureController(this.stream.controller);
|
|
554
|
-
this.isVideoConfigured = true;
|
|
555
|
-
return true;
|
|
556
|
-
}
|
|
557
|
-
// Configure a periodic refresh of our snapshot images.
|
|
558
|
-
configureSnapshotUpdates() {
|
|
559
|
-
// Set an ongoing refresh interval for our snapshot cache.
|
|
560
|
-
const interval = setInterval(() => {
|
|
561
|
-
// If we've removed the device, make sure we stop refreshing.
|
|
562
|
-
if (this.isDeleted) {
|
|
563
|
-
clearInterval(interval);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
// Refresh our snapshot cache.
|
|
567
|
-
void this.stream?.getSnapshot(undefined, false);
|
|
568
|
-
}, PROTECT_SNAPSHOT_CACHE_REFRESH_INTERVAL * 1000);
|
|
569
512
|
return true;
|
|
570
513
|
}
|
|
571
514
|
// Configure HomeKit Secure Video support.
|
|
@@ -581,75 +524,66 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
581
524
|
}
|
|
582
525
|
// Configure a switch to manually enable or disable HKSV recording for a camera.
|
|
583
526
|
configureHksvRecordingSwitch() {
|
|
584
|
-
//
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
this.accessory.removeService(switchService);
|
|
527
|
+
// Validate whether we should have this service enabled.
|
|
528
|
+
if (!this.validService(this.hap.Service.Switch, () => {
|
|
529
|
+
// If we don't have HKSV or the HKSV recording switch enabled, disable it and we're done.
|
|
530
|
+
if (!this.hasFeature("Video.HKSV.Recording.Switch")) {
|
|
531
|
+
return false;
|
|
590
532
|
}
|
|
533
|
+
return true;
|
|
534
|
+
}, ProtectReservedNames.SWITCH_HKSV_RECORDING)) {
|
|
591
535
|
// We want to default this back to recording whenever we disable the recording switch.
|
|
592
536
|
this.accessory.context.hksvRecording = true;
|
|
593
537
|
return false;
|
|
594
538
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
return false;
|
|
602
|
-
}
|
|
603
|
-
switchService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
604
|
-
this.accessory.addService(switchService);
|
|
539
|
+
// Acquire the service.
|
|
540
|
+
const service = this.acquireService(this.hap.Service.Switch, this.accessoryName + " HKSV Recording", ProtectReservedNames.SWITCH_HKSV_RECORDING);
|
|
541
|
+
// Fail gracefully.
|
|
542
|
+
if (!service) {
|
|
543
|
+
this.log.error("Unable to add HKSV recording switch.");
|
|
544
|
+
return false;
|
|
605
545
|
}
|
|
606
546
|
// Activate or deactivate HKSV recording.
|
|
607
|
-
|
|
608
|
-
return this.accessory.context.hksvRecording;
|
|
547
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
|
|
548
|
+
return this.accessory.context.hksvRecording ?? true;
|
|
609
549
|
});
|
|
610
|
-
|
|
550
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onSet((value) => {
|
|
611
551
|
if (this.accessory.context.hksvRecording !== value) {
|
|
612
|
-
this.log.info("
|
|
552
|
+
this.log.info("HKSV event recording has been %s.", value === true ? "enabled" : "disabled");
|
|
613
553
|
}
|
|
614
554
|
this.accessory.context.hksvRecording = value === true;
|
|
615
555
|
});
|
|
616
556
|
// Initialize the switch.
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
this.log.info("Enabling HomeKit Secure Video recording switch.");
|
|
557
|
+
service.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.hksvRecording);
|
|
558
|
+
this.log.info("Enabling HKSV recording switch.");
|
|
620
559
|
return true;
|
|
621
560
|
}
|
|
622
561
|
// Configure a switch to manually enable or disable dynamic bitrate capabilities for a camera.
|
|
623
562
|
configureDynamicBitrateSwitch() {
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
this.accessory.removeService(switchService);
|
|
563
|
+
// Validate whether we should have this service enabled.
|
|
564
|
+
if (!this.validService(this.hap.Service.Switch, () => {
|
|
565
|
+
// If we don't want a dynamic bitrate switch, disable it and we're done.
|
|
566
|
+
if (!this.hasFeature("Video.DynamicBitrate.Switch")) {
|
|
567
|
+
return false;
|
|
630
568
|
}
|
|
569
|
+
return true;
|
|
570
|
+
}, ProtectReservedNames.SWITCH_DYNAMIC_BITRATE)) {
|
|
631
571
|
// We want to default this back to off by default whenever we disable the dynamic bitrate switch.
|
|
632
572
|
this.accessory.context.dynamicBitrate = false;
|
|
633
573
|
return false;
|
|
634
574
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
switchService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
644
|
-
this.accessory.addService(switchService);
|
|
575
|
+
// Acquire the service.
|
|
576
|
+
const service = this.acquireService(this.hap.Service.Switch, this.accessoryName + " Dynamic Bitrate", ProtectReservedNames.SWITCH_DYNAMIC_BITRATE);
|
|
577
|
+
// Fail gracefully.
|
|
578
|
+
if (!service) {
|
|
579
|
+
this.log.error("Unable to add dynamic bitrate switch.");
|
|
580
|
+
return false;
|
|
645
581
|
}
|
|
646
582
|
// Activate or deactivate dynamic bitrate for this device.
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
})
|
|
652
|
-
.onSet(async (value) => {
|
|
583
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
|
|
584
|
+
return this.accessory.context.dynamicBitrate ?? false;
|
|
585
|
+
});
|
|
586
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onSet(async (value) => {
|
|
653
587
|
if (this.accessory.context.dynamicBitrate === value) {
|
|
654
588
|
return;
|
|
655
589
|
}
|
|
@@ -659,12 +593,8 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
659
593
|
this.log.info("Dynamic streaming bitrate adjustment on the UniFi Protect controller enabled.");
|
|
660
594
|
return;
|
|
661
595
|
}
|
|
662
|
-
// We're disabling dynamic bitrate for this device.
|
|
663
|
-
const updatedChannels = this.ufp.channels;
|
|
664
|
-
// Update the channels JSON.
|
|
665
|
-
for (const channel of updatedChannels) {
|
|
666
|
-
channel.bitrate = channel.maxBitrate;
|
|
667
|
-
}
|
|
596
|
+
// We're disabling dynamic bitrate for this device. Update the channels JSON to revert to the maximum bitrate we can.
|
|
597
|
+
const updatedChannels = this.ufp.channels.map(channel => ({ ...channel, bitrate: channel.maxBitrate }));
|
|
668
598
|
// Send the channels JSON to Protect.
|
|
669
599
|
const newDevice = await this.nvr.ufpApi.updateDevice(this.ufp, { channels: updatedChannels });
|
|
670
600
|
// We failed.
|
|
@@ -678,8 +608,7 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
678
608
|
this.log.info("Dynamic streaming bitrate adjustment on the UniFi Protect controller disabled.");
|
|
679
609
|
});
|
|
680
610
|
// Initialize the switch.
|
|
681
|
-
|
|
682
|
-
switchService.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.dynamicBitrate);
|
|
611
|
+
service.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.dynamicBitrate);
|
|
683
612
|
this.log.info("Enabling the dynamic streaming bitrate adjustment switch.");
|
|
684
613
|
return true;
|
|
685
614
|
}
|
|
@@ -689,31 +618,29 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
689
618
|
// The Protect controller supports three modes for recording on a camera: always, detections, and never. We create switches for each of the modes.
|
|
690
619
|
for (const ufpRecordingSwitchType of [ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
691
620
|
const ufpRecordingSetting = ufpRecordingSwitchType.slice(ufpRecordingSwitchType.lastIndexOf(".") + 1);
|
|
692
|
-
//
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
this.accessory.removeService(switchService);
|
|
621
|
+
// Validate whether we should have this service enabled.
|
|
622
|
+
if (!this.validService(this.hap.Service.Switch, () => {
|
|
623
|
+
// If we don't have the feature option enabled, disable the switch and we're done.
|
|
624
|
+
if (!this.hasFeature("Nvr.Recording.Switch")) {
|
|
625
|
+
return false;
|
|
698
626
|
}
|
|
627
|
+
return true;
|
|
628
|
+
}, ufpRecordingSwitchType)) {
|
|
699
629
|
continue;
|
|
700
630
|
}
|
|
701
|
-
const switchName = this.accessoryName + " UFP Recording " +
|
|
702
|
-
//
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
switchService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
710
|
-
this.accessory.addService(switchService);
|
|
631
|
+
const switchName = this.accessoryName + " UFP Recording " + toCamelCase(ufpRecordingSetting);
|
|
632
|
+
// Acquire the service.
|
|
633
|
+
const service = this.acquireService(this.hap.Service.Switch, switchName, ufpRecordingSwitchType);
|
|
634
|
+
// Fail gracefully.
|
|
635
|
+
if (!service) {
|
|
636
|
+
this.log.error("Unable to add UniFi Protect recording switches.");
|
|
637
|
+
continue;
|
|
711
638
|
}
|
|
712
639
|
// Activate or deactivate the appropriate recording mode on the Protect controller.
|
|
713
|
-
|
|
640
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
|
|
714
641
|
return this.ufp.recordingSettings.mode === ufpRecordingSetting;
|
|
715
642
|
});
|
|
716
|
-
|
|
643
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onSet(async (value) => {
|
|
717
644
|
// We only want to do something if we're being activated. Turning off the switch would really be an undefined state given that there are three different
|
|
718
645
|
// settings one can choose from. Instead, we do nothing and leave it to the user to choose what state they really want to set.
|
|
719
646
|
if (!value) {
|
|
@@ -745,8 +672,7 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
745
672
|
this.log.info("UniFi Protect recording mode set to %s.", ufpRecordingSetting);
|
|
746
673
|
});
|
|
747
674
|
// Initialize the recording switch state.
|
|
748
|
-
|
|
749
|
-
switchService.updateCharacteristic(this.hap.Characteristic.On, this.ufp.recordingSettings.mode === ufpRecordingSetting);
|
|
675
|
+
service.updateCharacteristic(this.hap.Characteristic.On, this.ufp.recordingSettings.mode === ufpRecordingSetting);
|
|
750
676
|
switchesEnabled.push(ufpRecordingSetting);
|
|
751
677
|
}
|
|
752
678
|
if (switchesEnabled.length) {
|
|
@@ -757,44 +683,28 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
757
683
|
// Configure MQTT capabilities of this camera.
|
|
758
684
|
configureMqtt() {
|
|
759
685
|
// Return the RTSP URLs when requested.
|
|
760
|
-
this.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
const urlInfo = {};
|
|
767
|
-
// Grab all the available RTSP channels.
|
|
768
|
-
for (const channel of this.ufp.channels) {
|
|
769
|
-
if (!channel.isRtspEnabled) {
|
|
770
|
-
continue;
|
|
771
|
-
}
|
|
772
|
-
urlInfo[channel.name] = "rtsps://" + this.nvr.ufp.host + ":" + this.nvr.ufp.ports.rtsp.toString() + "/" + channel.rtspAlias + "?enableSrtp";
|
|
773
|
-
}
|
|
774
|
-
this.nvr.mqtt?.publish(this.accessory, "rtsp", JSON.stringify(urlInfo));
|
|
775
|
-
this.log.info("RTSP information published via MQTT.");
|
|
686
|
+
this.subscribeGet("rtsp", "RTSP information", () => {
|
|
687
|
+
// Grab all the available RTSP channels and return them as a JSON.
|
|
688
|
+
return JSON.stringify(Object.assign({}, ...this.ufp.channels.filter(channel => channel.isRtspEnabled)
|
|
689
|
+
.map(channel => ({ [channel.name]: "rtsps://" + this.nvr.ufp.host + ":" + this.nvr.ufp.ports.rtsp + "/" + channel.rtspAlias + "?enableSrtp" }))));
|
|
776
690
|
});
|
|
777
691
|
// Trigger snapshots when requested.
|
|
778
|
-
this.
|
|
779
|
-
const value = message.toString();
|
|
692
|
+
this.subscribeSet("snapshot", "snapshot trigger", (value) => {
|
|
780
693
|
// When we get the right message, we trigger the snapshot request.
|
|
781
|
-
if (value
|
|
694
|
+
if (value !== "true") {
|
|
782
695
|
return;
|
|
783
696
|
}
|
|
784
697
|
void this.stream?.handleSnapshotRequest();
|
|
785
|
-
this.log.info("Snapshot triggered via MQTT.");
|
|
786
698
|
});
|
|
787
699
|
// Enable doorbell-specific MQTT capabilities only when we have a Protect doorbell or a doorbell trigger enabled.
|
|
788
700
|
if (this.ufp.featureFlags.isDoorbell || this.hasFeature("Doorbell.Trigger")) {
|
|
789
701
|
// Trigger doorbell when requested.
|
|
790
|
-
this.
|
|
791
|
-
const value = message.toString();
|
|
702
|
+
this.subscribeSet("doorbell", "doorbell ring trigger", (value) => {
|
|
792
703
|
// When we get the right message, we trigger the doorbell request.
|
|
793
|
-
if (value
|
|
704
|
+
if (value !== "true") {
|
|
794
705
|
return;
|
|
795
706
|
}
|
|
796
707
|
this.nvr.events.doorbellEventHandler(this, Date.now());
|
|
797
|
-
this.log.info("Doorbell ring event triggered via MQTT.");
|
|
798
708
|
});
|
|
799
709
|
}
|
|
800
710
|
return true;
|
|
@@ -828,9 +738,8 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
828
738
|
}
|
|
829
739
|
// Set the bitrate for a specific camera channel.
|
|
830
740
|
async setBitrate(channelId, value) {
|
|
831
|
-
// If we've disabled the ability to set the bitrate dynamically, silently fail. We prioritize switches over the global
|
|
832
|
-
// setting
|
|
833
|
-
// user has both the global setting and the switch enabled, the switch setting will take precedence.
|
|
741
|
+
// If we've disabled the ability to set the bitrate dynamically, silently fail. We prioritize switches over the global setting here, in case the user enabled both,
|
|
742
|
+
// using the principle that the most specific setting always wins. If the user has both the global setting and the switch enabled, the switch will take precedence.
|
|
834
743
|
if ((!this.accessory.context.dynamicBitrate && !this.hasFeature("Video.DynamicBitrate")) ||
|
|
835
744
|
(!this.accessory.context.dynamicBitrate && this.hasFeature("Video.DynamicBitrate") && this.hasFeature("Video.DynamicBitrate.Switch"))) {
|
|
836
745
|
return true;
|