homebridge-unifi-protect 6.19.0 → 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/protect-camera.d.ts +0 -2
- package/dist/devices/protect-camera.js +114 -189
- package/dist/devices/protect-camera.js.map +1 -1
- package/dist/devices/protect-chime.d.ts +3 -0
- package/dist/devices/protect-chime.js +100 -28
- package/dist/devices/protect-chime.js.map +1 -1
- package/dist/devices/protect-device.d.ts +7 -2
- package/dist/devices/protect-device.js +147 -98
- package/dist/devices/protect-device.js.map +1 -1
- package/dist/devices/protect-doorbell.js +48 -81
- package/dist/devices/protect-doorbell.js.map +1 -1
- package/dist/devices/protect-light.js +33 -43
- package/dist/devices/protect-light.js.map +1 -1
- package/dist/devices/protect-liveviews.js +10 -20
- package/dist/devices/protect-liveviews.js.map +1 -1
- package/dist/devices/protect-nvr-systeminfo.js +2 -8
- package/dist/devices/protect-nvr-systeminfo.js.map +1 -1
- package/dist/devices/protect-securitysystem.d.ts +1 -2
- package/dist/devices/protect-securitysystem.js +31 -58
- package/dist/devices/protect-securitysystem.js.map +1 -1
- package/dist/devices/protect-sensor.d.ts +2 -1
- package/dist/devices/protect-sensor.js +137 -184
- package/dist/devices/protect-sensor.js.map +1 -1
- package/dist/devices/protect-viewer.js +21 -28
- package/dist/devices/protect-viewer.js.map +1 -1
- package/dist/protect-mqtt.d.ts +2 -2
- package/dist/protect-mqtt.js +17 -3
- package/dist/protect-mqtt.js.map +1 -1
- package/dist/protect-nvr.js +5 -2
- package/dist/protect-nvr.js.map +1 -1
- package/dist/protect-options.js +6 -5
- package/dist/protect-options.js.map +1 -1
- package/dist/protect-record.js +8 -8
- package/dist/protect-record.js.map +1 -1
- package/dist/protect-snapshot.js +8 -0
- package/dist/protect-snapshot.js.map +1 -1
- package/dist/protect-types.d.ts +3 -0
- package/dist/protect-types.js +6 -0
- package/dist/protect-types.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +2 -0
- package/dist/settings.js.map +1 -1
- package/homebridge-ui/public/index.html +2 -0
- package/package.json +9 -9
|
@@ -13,9 +13,7 @@ export interface RtspEntry {
|
|
|
13
13
|
export declare class ProtectCamera extends ProtectDevice {
|
|
14
14
|
hasHksv: boolean;
|
|
15
15
|
private isDeleted;
|
|
16
|
-
private isDoorbellConfigured;
|
|
17
16
|
isRinging: boolean;
|
|
18
|
-
private isVideoConfigured;
|
|
19
17
|
detectLicensePlate: string[];
|
|
20
18
|
private rtspEntries;
|
|
21
19
|
private rtspQuality;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
import { ProtectReservedNames, toCamelCase } from "../protect-types.js";
|
|
1
2
|
import { PROTECT_HOMEKIT_IDR_INTERVAL } from "../settings.js";
|
|
2
3
|
import { ProtectDevice } from "./protect-device.js";
|
|
3
|
-
import { ProtectReservedNames } from "../protect-types.js";
|
|
4
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.
|
|
@@ -202,30 +183,22 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
202
183
|
}
|
|
203
184
|
// A utility for us to add contact sensors.
|
|
204
185
|
const addSmartDetectContactSensor = (name, serviceId, errorMessage) => {
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
if (!
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (!contactService) {
|
|
212
|
-
this.log.error(errorMessage);
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
contactService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
216
|
-
// Finally, add it to the camera.
|
|
217
|
-
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;
|
|
218
192
|
}
|
|
219
193
|
// Initialize the sensor.
|
|
220
|
-
|
|
221
|
-
contactService.updateCharacteristic(this.hap.Characteristic.ContactSensorState, false);
|
|
194
|
+
service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, false);
|
|
222
195
|
return true;
|
|
223
196
|
};
|
|
224
197
|
let enabledContactSensors = [];
|
|
225
198
|
// Add individual contact sensors for each object detection type, if needed.
|
|
226
199
|
if (this.hasFeature("Motion.SmartDetect.ObjectSensors")) {
|
|
227
200
|
for (const smartDetectType of this.ufp.featureFlags.smartDetectTypes) {
|
|
228
|
-
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.")) {
|
|
229
202
|
enabledContactSensors.push(smartDetectType);
|
|
230
203
|
}
|
|
231
204
|
}
|
|
@@ -248,17 +221,17 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
248
221
|
}
|
|
249
222
|
// Configure a switch to manually trigger a doorbell ring event for HomeKit.
|
|
250
223
|
configureDoorbellTrigger() {
|
|
251
|
-
// Find the switch service, if it exists.
|
|
252
|
-
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
|
|
253
224
|
// See if we have a doorbell service configured.
|
|
254
225
|
let doorbellService = this.accessory.getService(this.hap.Service.Doorbell);
|
|
255
|
-
//
|
|
256
|
-
if (!this.
|
|
257
|
-
|
|
258
|
-
|
|
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;
|
|
259
231
|
}
|
|
260
|
-
|
|
261
|
-
|
|
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.
|
|
262
235
|
if (!this.ufp.featureFlags.isDoorbell && doorbellService) {
|
|
263
236
|
this.accessory.removeService(doorbellService);
|
|
264
237
|
}
|
|
@@ -276,16 +249,12 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
276
249
|
return false;
|
|
277
250
|
}
|
|
278
251
|
}
|
|
279
|
-
const triggerName = this.accessoryName + " Doorbell Trigger";
|
|
280
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.
|
|
281
255
|
if (!triggerService) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
this.log.error("Unable to add the doorbell trigger.");
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
triggerService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
288
|
-
this.accessory.addService(triggerService);
|
|
256
|
+
this.log.error("Unable to add the doorbell trigger.");
|
|
257
|
+
return false;
|
|
289
258
|
}
|
|
290
259
|
// Trigger the doorbell.
|
|
291
260
|
triggerService.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
|
|
@@ -307,31 +276,21 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
307
276
|
}
|
|
308
277
|
});
|
|
309
278
|
// Initialize the switch.
|
|
310
|
-
triggerService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, triggerName);
|
|
311
279
|
triggerService.updateCharacteristic(this.hap.Characteristic.On, false);
|
|
312
280
|
this.log.info("Enabling doorbell automation trigger.");
|
|
313
281
|
return true;
|
|
314
282
|
}
|
|
315
283
|
// Configure the doorbell service for HomeKit.
|
|
316
284
|
configureVideoDoorbell() {
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// Add the doorbell service to this Protect doorbell. HomeKit requires the doorbell service to be
|
|
324
|
-
// marked as the primary service on the accessory.
|
|
325
|
-
if (!doorbellService) {
|
|
326
|
-
doorbellService = new this.hap.Service.Doorbell(this.accessoryName);
|
|
327
|
-
if (!doorbellService) {
|
|
328
|
-
this.log.error("Unable to add doorbell.");
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
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;
|
|
332
291
|
}
|
|
333
|
-
|
|
334
|
-
|
|
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);
|
|
335
294
|
return true;
|
|
336
295
|
}
|
|
337
296
|
// Configure additional camera-specific characteristics for HomeKit.
|
|
@@ -414,18 +373,15 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
414
373
|
// Figure out which camera channels are RTSP-enabled, and user-enabled.
|
|
415
374
|
let cameraChannels = this.ufp.channels.filter(x => x.isRtspEnabled && this.hasFeature("Video.Stream." + x.name, true));
|
|
416
375
|
// Make sure we've got a HomeKit compatible IDR frame interval. If not, let's take care of that.
|
|
417
|
-
|
|
376
|
+
const idrChannels = cameraChannels.filter(x => x.idrInterval !== PROTECT_HOMEKIT_IDR_INTERVAL);
|
|
418
377
|
if (idrChannels.length) {
|
|
419
|
-
// Edit the channel map.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
return x;
|
|
423
|
-
});
|
|
424
|
-
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;
|
|
425
381
|
}
|
|
426
382
|
// Set the camera and shapshot URLs.
|
|
427
383
|
const cameraUrl = "rtsps://" + (this.nvr.config.overrideAddress ?? this.ufp.connectionHost) + ":" + this.nvr.ufp.ports.rtsps.toString() + "/";
|
|
428
|
-
// Filter out any package camera entries.
|
|
384
|
+
// Filter out any package camera entries. We deal with those independently in the package camera class.
|
|
429
385
|
cameraChannels = cameraChannels.filter(x => x.name !== "Package Camera");
|
|
430
386
|
// No RTSP streams are available that meet our criteria - we're done.
|
|
431
387
|
if (!cameraChannels.length) {
|
|
@@ -516,8 +472,8 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
516
472
|
}
|
|
517
473
|
// Publish our updated list of supported resolutions and their URLs.
|
|
518
474
|
this.rtspEntries = rtspEntries;
|
|
519
|
-
// If we'
|
|
520
|
-
if (this.
|
|
475
|
+
// If we've already configured the HomeKit video streaming delegate, we're done here.
|
|
476
|
+
if (this.stream) {
|
|
521
477
|
return true;
|
|
522
478
|
}
|
|
523
479
|
// Inform users about our RTSP entry mapping, if we're debugging.
|
|
@@ -539,17 +495,20 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
539
495
|
}
|
|
540
496
|
// Inform the user if we've set a streaming default.
|
|
541
497
|
if (this.rtspQuality.StreamingDefault) {
|
|
542
|
-
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.");
|
|
543
503
|
}
|
|
544
504
|
// Inform the user if we've set a recording default.
|
|
545
505
|
if (this.rtspQuality.RecordingDefault) {
|
|
546
|
-
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));
|
|
547
507
|
}
|
|
548
508
|
// Configure the video stream with our resolutions.
|
|
549
509
|
this.stream = new ProtectStreamingDelegate(this, this.rtspEntries.map(x => x.resolution));
|
|
550
510
|
// Fire up the controller and inform HomeKit about it.
|
|
551
511
|
this.accessory.configureController(this.stream.controller);
|
|
552
|
-
this.isVideoConfigured = true;
|
|
553
512
|
return true;
|
|
554
513
|
}
|
|
555
514
|
// Configure HomeKit Secure Video support.
|
|
@@ -565,75 +524,66 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
565
524
|
}
|
|
566
525
|
// Configure a switch to manually enable or disable HKSV recording for a camera.
|
|
567
526
|
configureHksvRecordingSwitch() {
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
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;
|
|
574
532
|
}
|
|
533
|
+
return true;
|
|
534
|
+
}, ProtectReservedNames.SWITCH_HKSV_RECORDING)) {
|
|
575
535
|
// We want to default this back to recording whenever we disable the recording switch.
|
|
576
536
|
this.accessory.context.hksvRecording = true;
|
|
577
537
|
return false;
|
|
578
538
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
return false;
|
|
586
|
-
}
|
|
587
|
-
switchService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
588
|
-
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;
|
|
589
545
|
}
|
|
590
546
|
// Activate or deactivate HKSV recording.
|
|
591
|
-
|
|
592
|
-
return this.accessory.context.hksvRecording;
|
|
547
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
|
|
548
|
+
return this.accessory.context.hksvRecording ?? true;
|
|
593
549
|
});
|
|
594
|
-
|
|
550
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onSet((value) => {
|
|
595
551
|
if (this.accessory.context.hksvRecording !== value) {
|
|
596
|
-
this.log.info("
|
|
552
|
+
this.log.info("HKSV event recording has been %s.", value === true ? "enabled" : "disabled");
|
|
597
553
|
}
|
|
598
554
|
this.accessory.context.hksvRecording = value === true;
|
|
599
555
|
});
|
|
600
556
|
// Initialize the switch.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
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.");
|
|
604
559
|
return true;
|
|
605
560
|
}
|
|
606
561
|
// Configure a switch to manually enable or disable dynamic bitrate capabilities for a camera.
|
|
607
562
|
configureDynamicBitrateSwitch() {
|
|
608
|
-
//
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
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;
|
|
614
568
|
}
|
|
569
|
+
return true;
|
|
570
|
+
}, ProtectReservedNames.SWITCH_DYNAMIC_BITRATE)) {
|
|
615
571
|
// We want to default this back to off by default whenever we disable the dynamic bitrate switch.
|
|
616
572
|
this.accessory.context.dynamicBitrate = false;
|
|
617
573
|
return false;
|
|
618
574
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
return false;
|
|
626
|
-
}
|
|
627
|
-
switchService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
628
|
-
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;
|
|
629
581
|
}
|
|
630
582
|
// Activate or deactivate dynamic bitrate for this device.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
})
|
|
636
|
-
.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) => {
|
|
637
587
|
if (this.accessory.context.dynamicBitrate === value) {
|
|
638
588
|
return;
|
|
639
589
|
}
|
|
@@ -643,12 +593,8 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
643
593
|
this.log.info("Dynamic streaming bitrate adjustment on the UniFi Protect controller enabled.");
|
|
644
594
|
return;
|
|
645
595
|
}
|
|
646
|
-
// We're disabling dynamic bitrate for this device.
|
|
647
|
-
const updatedChannels = this.ufp.channels;
|
|
648
|
-
// Update the channels JSON.
|
|
649
|
-
for (const channel of updatedChannels) {
|
|
650
|
-
channel.bitrate = channel.maxBitrate;
|
|
651
|
-
}
|
|
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 }));
|
|
652
598
|
// Send the channels JSON to Protect.
|
|
653
599
|
const newDevice = await this.nvr.ufpApi.updateDevice(this.ufp, { channels: updatedChannels });
|
|
654
600
|
// We failed.
|
|
@@ -662,8 +608,7 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
662
608
|
this.log.info("Dynamic streaming bitrate adjustment on the UniFi Protect controller disabled.");
|
|
663
609
|
});
|
|
664
610
|
// Initialize the switch.
|
|
665
|
-
|
|
666
|
-
switchService.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.dynamicBitrate);
|
|
611
|
+
service.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.dynamicBitrate);
|
|
667
612
|
this.log.info("Enabling the dynamic streaming bitrate adjustment switch.");
|
|
668
613
|
return true;
|
|
669
614
|
}
|
|
@@ -673,31 +618,29 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
673
618
|
// The Protect controller supports three modes for recording on a camera: always, detections, and never. We create switches for each of the modes.
|
|
674
619
|
for (const ufpRecordingSwitchType of [ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
675
620
|
const ufpRecordingSetting = ufpRecordingSwitchType.slice(ufpRecordingSwitchType.lastIndexOf(".") + 1);
|
|
676
|
-
//
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
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;
|
|
682
626
|
}
|
|
627
|
+
return true;
|
|
628
|
+
}, ufpRecordingSwitchType)) {
|
|
683
629
|
continue;
|
|
684
630
|
}
|
|
685
|
-
const switchName = this.accessoryName + " UFP Recording " +
|
|
686
|
-
//
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
switchService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
|
|
694
|
-
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;
|
|
695
638
|
}
|
|
696
639
|
// Activate or deactivate the appropriate recording mode on the Protect controller.
|
|
697
|
-
|
|
640
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
|
|
698
641
|
return this.ufp.recordingSettings.mode === ufpRecordingSetting;
|
|
699
642
|
});
|
|
700
|
-
|
|
643
|
+
service.getCharacteristic(this.hap.Characteristic.On)?.onSet(async (value) => {
|
|
701
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
|
|
702
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.
|
|
703
646
|
if (!value) {
|
|
@@ -729,8 +672,7 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
729
672
|
this.log.info("UniFi Protect recording mode set to %s.", ufpRecordingSetting);
|
|
730
673
|
});
|
|
731
674
|
// Initialize the recording switch state.
|
|
732
|
-
|
|
733
|
-
switchService.updateCharacteristic(this.hap.Characteristic.On, this.ufp.recordingSettings.mode === ufpRecordingSetting);
|
|
675
|
+
service.updateCharacteristic(this.hap.Characteristic.On, this.ufp.recordingSettings.mode === ufpRecordingSetting);
|
|
734
676
|
switchesEnabled.push(ufpRecordingSetting);
|
|
735
677
|
}
|
|
736
678
|
if (switchesEnabled.length) {
|
|
@@ -741,44 +683,28 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
741
683
|
// Configure MQTT capabilities of this camera.
|
|
742
684
|
configureMqtt() {
|
|
743
685
|
// Return the RTSP URLs when requested.
|
|
744
|
-
this.
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
const urlInfo = {};
|
|
751
|
-
// Grab all the available RTSP channels.
|
|
752
|
-
for (const channel of this.ufp.channels) {
|
|
753
|
-
if (!channel.isRtspEnabled) {
|
|
754
|
-
continue;
|
|
755
|
-
}
|
|
756
|
-
urlInfo[channel.name] = "rtsps://" + this.nvr.ufp.host + ":" + this.nvr.ufp.ports.rtsp.toString() + "/" + channel.rtspAlias + "?enableSrtp";
|
|
757
|
-
}
|
|
758
|
-
this.nvr.mqtt?.publish(this.accessory, "rtsp", JSON.stringify(urlInfo));
|
|
759
|
-
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" }))));
|
|
760
690
|
});
|
|
761
691
|
// Trigger snapshots when requested.
|
|
762
|
-
this.
|
|
763
|
-
const value = message.toString();
|
|
692
|
+
this.subscribeSet("snapshot", "snapshot trigger", (value) => {
|
|
764
693
|
// When we get the right message, we trigger the snapshot request.
|
|
765
|
-
if (value
|
|
694
|
+
if (value !== "true") {
|
|
766
695
|
return;
|
|
767
696
|
}
|
|
768
697
|
void this.stream?.handleSnapshotRequest();
|
|
769
|
-
this.log.info("Snapshot triggered via MQTT.");
|
|
770
698
|
});
|
|
771
699
|
// Enable doorbell-specific MQTT capabilities only when we have a Protect doorbell or a doorbell trigger enabled.
|
|
772
700
|
if (this.ufp.featureFlags.isDoorbell || this.hasFeature("Doorbell.Trigger")) {
|
|
773
701
|
// Trigger doorbell when requested.
|
|
774
|
-
this.
|
|
775
|
-
const value = message.toString();
|
|
702
|
+
this.subscribeSet("doorbell", "doorbell ring trigger", (value) => {
|
|
776
703
|
// When we get the right message, we trigger the doorbell request.
|
|
777
|
-
if (value
|
|
704
|
+
if (value !== "true") {
|
|
778
705
|
return;
|
|
779
706
|
}
|
|
780
707
|
this.nvr.events.doorbellEventHandler(this, Date.now());
|
|
781
|
-
this.log.info("Doorbell ring event triggered via MQTT.");
|
|
782
708
|
});
|
|
783
709
|
}
|
|
784
710
|
return true;
|
|
@@ -812,9 +738,8 @@ export class ProtectCamera extends ProtectDevice {
|
|
|
812
738
|
}
|
|
813
739
|
// Set the bitrate for a specific camera channel.
|
|
814
740
|
async setBitrate(channelId, value) {
|
|
815
|
-
// If we've disabled the ability to set the bitrate dynamically, silently fail. We prioritize switches over the global
|
|
816
|
-
// setting
|
|
817
|
-
// 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.
|
|
818
743
|
if ((!this.accessory.context.dynamicBitrate && !this.hasFeature("Video.DynamicBitrate")) ||
|
|
819
744
|
(!this.accessory.context.dynamicBitrate && this.hasFeature("Video.DynamicBitrate") && this.hasFeature("Video.DynamicBitrate.Switch"))) {
|
|
820
745
|
return true;
|