homebridge-unifi-protect 5.3.2 → 5.4.1
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/config.schema.json +1 -1
- package/dist/protect-accessory.js +37 -36
- package/dist/protect-accessory.js.map +1 -1
- package/dist/protect-camera.js +268 -92
- package/dist/protect-camera.js.map +1 -1
- package/dist/protect-ffmpeg-record.js +55 -40
- package/dist/protect-ffmpeg-record.js.map +1 -1
- package/dist/protect-ffmpeg-stream.js +1 -3
- package/dist/protect-ffmpeg-stream.js.map +1 -1
- package/dist/protect-ffmpeg.js +30 -14
- package/dist/protect-ffmpeg.js.map +1 -1
- package/dist/protect-nvr-events.js +19 -12
- package/dist/protect-nvr-events.js.map +1 -1
- package/dist/protect-record.js +143 -72
- package/dist/protect-record.js.map +1 -1
- package/dist/protect-securitysystem.js +1 -1
- package/dist/protect-securitysystem.js.map +1 -1
- package/dist/protect-sensor.js +4 -4
- package/dist/protect-sensor.js.map +1 -1
- package/dist/protect-stream.js +61 -60
- package/dist/protect-stream.js.map +1 -1
- package/dist/protect-timeshift.js +46 -62
- package/dist/protect-timeshift.js.map +1 -1
- package/dist/settings.js +5 -6
- package/dist/settings.js.map +1 -1
- package/package.json +9 -9
package/dist/protect-camera.js
CHANGED
|
@@ -26,6 +26,12 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
26
26
|
if (this.accessory.context.detectMotion !== undefined) {
|
|
27
27
|
detectMotion = this.accessory.context.detectMotion;
|
|
28
28
|
}
|
|
29
|
+
// Default to disabling the dynamic bitrate setting.
|
|
30
|
+
let dynamicBitrate = false;
|
|
31
|
+
// Save the dynamic bitrate switch state before we wipeout the context.
|
|
32
|
+
if (this.accessory.context.dynamicBitrate !== undefined) {
|
|
33
|
+
dynamicBitrate = this.accessory.context.dynamicBitrate;
|
|
34
|
+
}
|
|
29
35
|
// Default to enabling HKSV recording.
|
|
30
36
|
let hksvRecording = true;
|
|
31
37
|
// Save the HKSV recording switch state before we wipeout the context.
|
|
@@ -37,10 +43,11 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
37
43
|
this.accessory.context.device = device;
|
|
38
44
|
this.accessory.context.nvr = (_a = this.nvr.nvrApi.bootstrap) === null || _a === void 0 ? void 0 : _a.nvr.mac;
|
|
39
45
|
this.accessory.context.detectMotion = detectMotion;
|
|
46
|
+
this.accessory.context.dynamicBitrate = dynamicBitrate;
|
|
40
47
|
this.accessory.context.hksvRecording = hksvRecording;
|
|
41
|
-
// Inform the user if we have enabled dynamic
|
|
42
|
-
if ((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Video.
|
|
43
|
-
this.log.info("%s: Dynamic
|
|
48
|
+
// Inform the user if we have enabled the dynamic bitrate setting.
|
|
49
|
+
if ((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Video.DynamicBitrate", false)) {
|
|
50
|
+
this.log.info("%s: Dynamic streaming bitrate adjustment on the UniFi Protect controller enabled.", this.name());
|
|
44
51
|
}
|
|
45
52
|
// If the camera supports it, check to see if we have smart motion events enabled.
|
|
46
53
|
if (device.featureFlags.hasSmartDetect && ((_c = this.nvr) === null || _c === void 0 ? void 0 : _c.optionEnabled(device, "Motion.SmartDetect", false))) {
|
|
@@ -74,6 +81,10 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
74
81
|
await this.configureVideoStream();
|
|
75
82
|
// Configure our camera details.
|
|
76
83
|
this.configureCameraDetails();
|
|
84
|
+
// Configure our bitrate switch.
|
|
85
|
+
this.configureDynamicBitrateSwitch();
|
|
86
|
+
// Configure our NVR recording switches.
|
|
87
|
+
this.configureNvrRecordingSwitch();
|
|
77
88
|
// Configure the doorbell trigger.
|
|
78
89
|
this.configureDoorbellTrigger();
|
|
79
90
|
return true;
|
|
@@ -83,7 +94,7 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
83
94
|
var _a, _b, _c, _d, _e, _f;
|
|
84
95
|
const device = this.accessory.context.device;
|
|
85
96
|
// Check for object-centric contact sensors that are no longer enabled and remove them.
|
|
86
|
-
for (const objectService of this.accessory.services.filter(x => { var _a; return (_a = x.subtype) === null || _a === void 0 ? void 0 : _a.startsWith(protect_accessory_1.
|
|
97
|
+
for (const objectService of this.accessory.services.filter(x => { var _a; return (_a = x.subtype) === null || _a === void 0 ? void 0 : _a.startsWith(protect_accessory_1.ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "."); })) {
|
|
87
98
|
// If we have motion sensors as well as object contact sensors enabled, and we have this object type enabled on this camera, we're good here.
|
|
88
99
|
if (((_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(device, "Motion.Sensor")) &&
|
|
89
100
|
((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(device, "Motion.SmartDetect.ObjectSensors", false))) {
|
|
@@ -104,10 +115,10 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
104
115
|
// Add individual contact sensors for each object detection type, if needed.
|
|
105
116
|
for (const smartDetectType of device.featureFlags.smartDetectTypes) {
|
|
106
117
|
// See if we already have this contact sensor configured.
|
|
107
|
-
let contactService = this.accessory.getServiceById(this.hap.Service.ContactSensor, protect_accessory_1.
|
|
118
|
+
let contactService = this.accessory.getServiceById(this.hap.Service.ContactSensor, protect_accessory_1.ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + smartDetectType);
|
|
108
119
|
// If not, let's add it.
|
|
109
120
|
if (!contactService) {
|
|
110
|
-
contactService = new this.hap.Service.ContactSensor(this.accessory.displayName + " " + smartDetectType.charAt(0).toUpperCase() + smartDetectType.slice(1), protect_accessory_1.
|
|
121
|
+
contactService = new this.hap.Service.ContactSensor(this.accessory.displayName + " " + smartDetectType.charAt(0).toUpperCase() + smartDetectType.slice(1), protect_accessory_1.ProtectReservedNames.CONTACT_MOTION_SMARTDETECT + "." + smartDetectType);
|
|
111
122
|
// Something went wrong, we're done here.
|
|
112
123
|
if (!contactService) {
|
|
113
124
|
this.log.error("%s: Unable to add smart motion contact sensor for %s detection.", this.name(), smartDetectType);
|
|
@@ -126,7 +137,7 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
126
137
|
configureMotionTrigger() {
|
|
127
138
|
var _a, _b, _c;
|
|
128
139
|
// Find the switch service, if it exists.
|
|
129
|
-
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.
|
|
140
|
+
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_MOTION_TRIGGER);
|
|
130
141
|
// Motion triggers are disabled by default and primarily exist for automation purposes.
|
|
131
142
|
if (!((_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(this.accessory.context.device, "Motion.Sensor")) ||
|
|
132
143
|
!((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Motion.Trigger", false))) {
|
|
@@ -137,7 +148,7 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
137
148
|
}
|
|
138
149
|
// Add the switch to the camera, if needed.
|
|
139
150
|
if (!triggerService) {
|
|
140
|
-
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Motion Trigger", protect_accessory_1.
|
|
151
|
+
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Motion Trigger", protect_accessory_1.ProtectReservedNames.SWITCH_MOTION_TRIGGER);
|
|
141
152
|
if (!triggerService) {
|
|
142
153
|
this.log.error("%s: Unable to add motion sensor trigger.", this.name());
|
|
143
154
|
return false;
|
|
@@ -145,7 +156,7 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
145
156
|
this.accessory.addService(triggerService);
|
|
146
157
|
}
|
|
147
158
|
const motionService = this.accessory.getService(this.hap.Service.MotionSensor);
|
|
148
|
-
const switchService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.
|
|
159
|
+
const switchService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_MOTION_SENSOR);
|
|
149
160
|
// Activate or deactivate motion detection.
|
|
150
161
|
(_c = triggerService
|
|
151
162
|
.getCharacteristic(this.hap.Characteristic.On)) === null || _c === void 0 ? void 0 : _c.onGet(() => {
|
|
@@ -183,7 +194,7 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
183
194
|
var _a, _b;
|
|
184
195
|
const camera = this.accessory.context.device;
|
|
185
196
|
// Find the switch service, if it exists.
|
|
186
|
-
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.
|
|
197
|
+
let triggerService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
|
|
187
198
|
// See if we have a doorbell service configured.
|
|
188
199
|
let doorbellService = this.accessory.getService(this.hap.Service.Doorbell);
|
|
189
200
|
// Doorbell switches are disabled by default and primarily exist for automation purposes.
|
|
@@ -213,7 +224,7 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
213
224
|
}
|
|
214
225
|
// Add the switch to the camera, if needed.
|
|
215
226
|
if (!triggerService) {
|
|
216
|
-
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Doorbell Trigger", protect_accessory_1.
|
|
227
|
+
triggerService = new this.hap.Service.Switch(this.accessory.displayName + " Doorbell Trigger", protect_accessory_1.ProtectReservedNames.SWITCH_DOORBELL_TRIGGER);
|
|
217
228
|
if (!triggerService) {
|
|
218
229
|
this.log.error("%s: Unable to add the doorbell trigger.", this.name());
|
|
219
230
|
return false;
|
|
@@ -305,69 +316,6 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
305
316
|
}
|
|
306
317
|
return true;
|
|
307
318
|
}
|
|
308
|
-
// Find an RTSP configuration for a given target resolution.
|
|
309
|
-
findRtsp(width, height, camera = null, address = "", rtspEntries = this.rtspEntries) {
|
|
310
|
-
var _a, _b;
|
|
311
|
-
// No RTSP entries to choose from, we're done.
|
|
312
|
-
if (!rtspEntries || !rtspEntries.length) {
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
// First, we check to see if we've set an explicit preference for the target address.
|
|
316
|
-
if (camera && address) {
|
|
317
|
-
// If we don't have this address cached, look it up and cache it.
|
|
318
|
-
if (!this.rtspQuality[address]) {
|
|
319
|
-
// Check to see if there's an explicit preference set and cache the result.
|
|
320
|
-
if (this.nvr.optionEnabled(camera, "Video.Stream.Only.Low", false, address, true)) {
|
|
321
|
-
this.rtspQuality[address] = "LOW";
|
|
322
|
-
}
|
|
323
|
-
else if (this.nvr.optionEnabled(camera, "Video.Stream.Only.Medium", false, address, true)) {
|
|
324
|
-
this.rtspQuality[address] = "MEDIUM";
|
|
325
|
-
}
|
|
326
|
-
else if (this.nvr.optionEnabled(camera, "Video.Stream.Only.High", false, address, true)) {
|
|
327
|
-
this.rtspQuality[address] = "HIGH";
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
this.rtspQuality[address] = "None";
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
// If it's set to none, we default to our normal lookup logic.
|
|
334
|
-
if (this.rtspQuality[address] !== "None") {
|
|
335
|
-
return (_a = rtspEntries.find(x => x.channel.name.toUpperCase() === this.rtspQuality[address])) !== null && _a !== void 0 ? _a : null;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
// Second, we check to see if we've set an explicit preference for stream quality.
|
|
339
|
-
if (this.rtspQuality.Default) {
|
|
340
|
-
return (_b = rtspEntries.find(x => x.channel.name.toUpperCase() === this.rtspQuality.Default)) !== null && _b !== void 0 ? _b : null;
|
|
341
|
-
}
|
|
342
|
-
// See if we have a match for our desired resolution on the camera. We ignore FPS - HomeKit clients seem
|
|
343
|
-
// to be able to handle it just fine.
|
|
344
|
-
const exactRtsp = rtspEntries.find(x => (x.resolution[0] === width) && (x.resolution[1] === height));
|
|
345
|
-
if (exactRtsp) {
|
|
346
|
-
return exactRtsp;
|
|
347
|
-
}
|
|
348
|
-
// No match found, let's see what we have that's closest. We try to be a bit smart about how we select our
|
|
349
|
-
// stream - if it's an HD quality stream request (720p+), we want to try to return something that's HD quality
|
|
350
|
-
// before looking for something lower resolution.
|
|
351
|
-
if ((width >= 1280) && (height >= 720)) {
|
|
352
|
-
for (const entry of rtspEntries) {
|
|
353
|
-
// Make sure we're looking at an HD resolution.
|
|
354
|
-
if (entry.resolution[0] < 1280) {
|
|
355
|
-
continue;
|
|
356
|
-
}
|
|
357
|
-
// Return the first one we find.
|
|
358
|
-
return entry;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// If we didn't request an HD resolution, or we couldn't find anything HD to use, we try to find whatever we
|
|
362
|
-
// can find that's close.
|
|
363
|
-
for (const entry of rtspEntries) {
|
|
364
|
-
if (width >= entry.resolution[0]) {
|
|
365
|
-
return entry;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
// We couldn't find a close match, return the lowest resolution we found.
|
|
369
|
-
return rtspEntries[rtspEntries.length - 1];
|
|
370
|
-
}
|
|
371
319
|
// Configure a camera accessory for HomeKit.
|
|
372
320
|
async configureVideoStream() {
|
|
373
321
|
var _a, _b;
|
|
@@ -448,19 +396,24 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
448
396
|
this.log.info("%s: Mapping resolution: %s.", this.name(), this.getResolution(entry.resolution) + " => " + entry.name);
|
|
449
397
|
}
|
|
450
398
|
}
|
|
451
|
-
// Check
|
|
452
|
-
|
|
453
|
-
this.
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
399
|
+
// Check for explicit RTSP profile preferences.
|
|
400
|
+
for (const rtspProfile of ["LOW", "MEDIUM", "HIGH"]) {
|
|
401
|
+
// Check to see if the user has requested a specific streaming profile for this camera.
|
|
402
|
+
if (nvr.optionEnabled(device, "Video.Stream.Only." + rtspProfile, false)) {
|
|
403
|
+
this.rtspQuality.StreamingDefault = rtspProfile;
|
|
404
|
+
}
|
|
405
|
+
// Check to see if the user has requested a specific recording profile for this camera.
|
|
406
|
+
if (nvr.optionEnabled(device, "Video.HKSV.Recording.Only." + rtspProfile, false)) {
|
|
407
|
+
this.rtspQuality.RecordingDefault = rtspProfile;
|
|
408
|
+
}
|
|
457
409
|
}
|
|
458
|
-
|
|
459
|
-
|
|
410
|
+
// Inform the user if we've set a streaming default.
|
|
411
|
+
if (this.rtspQuality.StreamingDefault) {
|
|
412
|
+
this.log.info("%s: Video streaming configured to use only: %s.", this.name(), this.rtspQuality.StreamingDefault.charAt(0) + this.rtspQuality.StreamingDefault.slice(1).toLowerCase());
|
|
460
413
|
}
|
|
461
|
-
// Inform the user if we've set a default.
|
|
462
|
-
if (this.rtspQuality.
|
|
463
|
-
this.log.info("%s:
|
|
414
|
+
// Inform the user if we've set a recording default.
|
|
415
|
+
if (this.rtspQuality.RecordingDefault) {
|
|
416
|
+
this.log.info("%s: HomeKit Secure Video event recording configured to use only: %s.", this.name(), this.rtspQuality.RecordingDefault.charAt(0) + this.rtspQuality.RecordingDefault.slice(1).toLowerCase());
|
|
464
417
|
}
|
|
465
418
|
// Configure the video stream with our resolutions.
|
|
466
419
|
this.stream = new protect_stream_1.ProtectStreamingDelegate(this, this.rtspEntries.map(x => x.resolution));
|
|
@@ -498,20 +451,20 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
498
451
|
configureHksvRecordingSwitch() {
|
|
499
452
|
var _a, _b, _c;
|
|
500
453
|
// Find the switch service, if it exists.
|
|
501
|
-
let switchService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.
|
|
502
|
-
// If we don't have HKSV or the HKSV recording switch, disable
|
|
454
|
+
let switchService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_HKSV_RECORDING);
|
|
455
|
+
// If we don't have HKSV or the HKSV recording switch enabled, disable it and we're done.
|
|
503
456
|
if (!((_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(this.accessory.context.device, "Video.HKSV")) ||
|
|
504
457
|
!((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Video.HKSV.Recording.Switch", false))) {
|
|
505
458
|
if (switchService) {
|
|
506
459
|
this.accessory.removeService(switchService);
|
|
507
460
|
}
|
|
508
461
|
// We want to default this back to recording whenever we disable the recording switch.
|
|
509
|
-
this.accessory.context.hksvRecording;
|
|
462
|
+
this.accessory.context.hksvRecording = true;
|
|
510
463
|
return false;
|
|
511
464
|
}
|
|
512
465
|
// Add the switch to the camera, if needed.
|
|
513
466
|
if (!switchService) {
|
|
514
|
-
switchService = new this.hap.Service.Switch(this.accessory.displayName + " HKSV Recording", protect_accessory_1.
|
|
467
|
+
switchService = new this.hap.Service.Switch(this.accessory.displayName + " HKSV Recording", protect_accessory_1.ProtectReservedNames.SWITCH_HKSV_RECORDING);
|
|
515
468
|
if (!switchService) {
|
|
516
469
|
this.log.error("%s: Unable to add the HomeKit Secure Video recording switch.", this.name());
|
|
517
470
|
return false;
|
|
@@ -533,6 +486,139 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
533
486
|
this.log.info("%s: Enabling HomeKit Secure Video recording switch.", this.name());
|
|
534
487
|
return true;
|
|
535
488
|
}
|
|
489
|
+
// Configure a switch to manually enable or disable dynamic bitrate capabilities for a camera.
|
|
490
|
+
configureDynamicBitrateSwitch() {
|
|
491
|
+
var _a, _b;
|
|
492
|
+
// Find the switch service, if it exists.
|
|
493
|
+
let switchService = this.accessory.getServiceById(this.hap.Service.Switch, protect_accessory_1.ProtectReservedNames.SWITCH_DYNAMIC_BITRATE);
|
|
494
|
+
// If we don't want a dynamic bitrate switch, disable it and we're done.
|
|
495
|
+
if (!((_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(this.accessory.context.device, "Video.DynamicBitrate.Switch", false))) {
|
|
496
|
+
if (switchService) {
|
|
497
|
+
this.accessory.removeService(switchService);
|
|
498
|
+
}
|
|
499
|
+
// We want to default this back to off by default whenever we disable the dynamic bitrate switch.
|
|
500
|
+
this.accessory.context.dynamicBitrate = false;
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
// Add the switch to the camera, if needed.
|
|
504
|
+
if (!switchService) {
|
|
505
|
+
switchService = new this.hap.Service.Switch(this.accessory.displayName + " Dynamic Bitrate", protect_accessory_1.ProtectReservedNames.SWITCH_DYNAMIC_BITRATE);
|
|
506
|
+
if (!switchService) {
|
|
507
|
+
this.log.error("%s: Unable to add the dynamic bitrate switch.", this.name());
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
this.accessory.addService(switchService);
|
|
511
|
+
}
|
|
512
|
+
// Activate or deactivate dynamic bitrate for this device.
|
|
513
|
+
(_b = switchService
|
|
514
|
+
.getCharacteristic(this.hap.Characteristic.On)) === null || _b === void 0 ? void 0 : _b.onGet(() => {
|
|
515
|
+
return this.accessory.context.dynamicBitrate;
|
|
516
|
+
}).onSet(async (value) => {
|
|
517
|
+
if (this.accessory.context.dynamicBitrate === value) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
// We're enabling dynamic bitrate for this device.
|
|
521
|
+
if (value) {
|
|
522
|
+
this.accessory.context.dynamicBitrate = true;
|
|
523
|
+
this.log.info("%s: Dynamic streaming bitrate adjustment on the UniFi Protect controller enabled.", this.name());
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
// We're disabling dynamic bitrate for this device.
|
|
527
|
+
const device = this.accessory.context.device;
|
|
528
|
+
const updatedChannels = device.channels;
|
|
529
|
+
// Update the channels JSON.
|
|
530
|
+
for (const channel of updatedChannels) {
|
|
531
|
+
channel.bitrate = channel.maxBitrate;
|
|
532
|
+
}
|
|
533
|
+
// Send the channels JSON to Protect.
|
|
534
|
+
const newDevice = await this.nvrApi.updateCamera(device, { channels: updatedChannels });
|
|
535
|
+
// We failed.
|
|
536
|
+
if (!newDevice) {
|
|
537
|
+
this.log.error("%s: Unable to set the streaming bitrate to %s.", this.name(), value);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
this.accessory.context.device = newDevice;
|
|
541
|
+
}
|
|
542
|
+
this.accessory.context.dynamicBitrate = false;
|
|
543
|
+
this.log.info("%s: Dynamic streaming bitrate adjustment on the UniFi Protect controller disabled.", this.name());
|
|
544
|
+
});
|
|
545
|
+
// Initialize the switch.
|
|
546
|
+
switchService.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.dynamicBitrate);
|
|
547
|
+
this.log.info("%s: Enabling the dynamic streaming bitrate adjustment switch.", this.name());
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
// Configure a series of switches to manually enable or disable recording on the UniFi Protect controller for a camera.
|
|
551
|
+
configureNvrRecordingSwitch() {
|
|
552
|
+
var _a, _b;
|
|
553
|
+
const switchesEnabled = [];
|
|
554
|
+
// The Protect controller supports three modes for recording on a camera: always, detections, and never. We create switches for each of the modes.
|
|
555
|
+
for (const ufpRecordingSwitchType of [protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
556
|
+
const ufpRecordingSetting = ufpRecordingSwitchType.slice(ufpRecordingSwitchType.lastIndexOf(".") + 1);
|
|
557
|
+
// Find the switch service, if it exists.
|
|
558
|
+
let switchService = this.accessory.getServiceById(this.hap.Service.Switch, ufpRecordingSwitchType);
|
|
559
|
+
// If we don't have the feature option enabled, disable the switch and we're done.
|
|
560
|
+
if (!((_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(this.accessory.context.device, "Nvr.Recording.Switch", false))) {
|
|
561
|
+
if (switchService) {
|
|
562
|
+
this.accessory.removeService(switchService);
|
|
563
|
+
}
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
// Add the switch to the camera, if needed.
|
|
567
|
+
if (!switchService) {
|
|
568
|
+
switchService = new this.hap.Service.Switch(this.accessory.displayName + " UFP Recording " + ufpRecordingSetting.charAt(0).toUpperCase() + ufpRecordingSetting.slice(1), ufpRecordingSwitchType);
|
|
569
|
+
if (!switchService) {
|
|
570
|
+
this.log.error("%s: Unable to add the UniFi Protect recording switches.", this.name());
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
this.accessory.addService(switchService);
|
|
574
|
+
}
|
|
575
|
+
// Activate or deactivate the appropriate recording mode on the Protect controller.
|
|
576
|
+
(_b = switchService
|
|
577
|
+
.getCharacteristic(this.hap.Characteristic.On)) === null || _b === void 0 ? void 0 : _b.onGet(() => {
|
|
578
|
+
return this.accessory.context.device.recordingSettings.mode === ufpRecordingSetting;
|
|
579
|
+
}).onSet(async (value) => {
|
|
580
|
+
var _a;
|
|
581
|
+
// We only want to do something if we're being activated. Turning off the switch would really be an undefined state given that
|
|
582
|
+
// there are three different settings one can choose from. Instead, we do nothing and leave it to the user to choose what state
|
|
583
|
+
// they really want to set.
|
|
584
|
+
if (!value) {
|
|
585
|
+
setTimeout(() => {
|
|
586
|
+
this.updateDevice();
|
|
587
|
+
}, 50);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
// Set our recording mode.
|
|
591
|
+
const device = this.accessory.context.device;
|
|
592
|
+
device.recordingSettings.mode = ufpRecordingSetting;
|
|
593
|
+
// Tell Protect about it.
|
|
594
|
+
const newDevice = await this.nvr.nvrApi.updateCamera(device, { recordingSettings: device.recordingSettings });
|
|
595
|
+
if (!newDevice) {
|
|
596
|
+
this.log.error("%s: Unable to set the UniFi Protect recording mode to %s.", this.name(), ufpRecordingSetting);
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
// Save our updated device context.
|
|
600
|
+
this.accessory.context.device = newDevice;
|
|
601
|
+
// Update all the other recording switches.
|
|
602
|
+
for (const otherUfpSwitch of [protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
603
|
+
// Don't update ourselves a second time.
|
|
604
|
+
if (ufpRecordingSwitchType === otherUfpSwitch) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
// Update the other recording switches.
|
|
608
|
+
(_a = this.accessory.getServiceById(this.hap.Service.Switch, otherUfpSwitch)) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.hap.Characteristic.On, false);
|
|
609
|
+
}
|
|
610
|
+
// Inform the user, and we're done.
|
|
611
|
+
this.log.info("%s: UniFi Protect recording mode set to %s.", this.name(), ufpRecordingSetting);
|
|
612
|
+
});
|
|
613
|
+
// Initialize the recording switch state.
|
|
614
|
+
switchService.updateCharacteristic(this.hap.Characteristic.On, this.accessory.context.device.recordingSettings.mode === ufpRecordingSetting);
|
|
615
|
+
switchesEnabled.push(ufpRecordingSetting);
|
|
616
|
+
}
|
|
617
|
+
if (switchesEnabled.length) {
|
|
618
|
+
this.log.info("%s: Enabling UniFi Protect recording switches: %s.", this.name(), switchesEnabled.join(", "));
|
|
619
|
+
}
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
536
622
|
// Configure MQTT capabilities of this camera.
|
|
537
623
|
configureMqtt() {
|
|
538
624
|
var _a, _b;
|
|
@@ -572,16 +658,29 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
572
658
|
}
|
|
573
659
|
// Refresh camera-specific characteristics.
|
|
574
660
|
updateDevice() {
|
|
661
|
+
var _a, _b, _c;
|
|
575
662
|
// Grab our device context.
|
|
576
663
|
const device = this.accessory.context.device;
|
|
664
|
+
// Update the camera state.
|
|
665
|
+
(_a = this.accessory.getService(this.hap.Service.MotionSensor)) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.hap.Characteristic.StatusActive, device.state === "CONNECTED");
|
|
577
666
|
// Find the service, if it exists.
|
|
578
667
|
const service = this.accessory.getService(this.hap.Service.CameraOperatingMode);
|
|
579
668
|
// Check to see if this device has a status light.
|
|
580
669
|
if (device.featureFlags.hasLedStatus) {
|
|
581
670
|
service === null || service === void 0 ? void 0 : service.updateCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator, device.ledSettings.isEnabled === true);
|
|
582
671
|
}
|
|
672
|
+
// Check for updates to the recording state, if we have the switches configured.
|
|
673
|
+
if ((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(device, "Nvr.Recording.Switch", false)) {
|
|
674
|
+
// Update all the switch states.
|
|
675
|
+
for (const ufpRecordingSwitchType of [protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_ALWAYS, protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_DETECTIONS, protect_accessory_1.ProtectReservedNames.SWITCH_UFP_RECORDING_NEVER]) {
|
|
676
|
+
const ufpRecordingSetting = ufpRecordingSwitchType.slice(ufpRecordingSwitchType.lastIndexOf(".") + 1);
|
|
677
|
+
// Update state based on the recording mode.
|
|
678
|
+
(_c = this.accessory.getServiceById(this.hap.Service.Switch, ufpRecordingSwitchType)) === null || _c === void 0 ? void 0 : _c.updateCharacteristic(this.hap.Characteristic.On, ufpRecordingSetting === device.recordingSettings.mode);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
583
681
|
return true;
|
|
584
682
|
}
|
|
683
|
+
// Get the current bitrate for a specific camera channel.
|
|
585
684
|
getBitrate(channelId) {
|
|
586
685
|
var _a;
|
|
587
686
|
// Grab the device JSON.
|
|
@@ -592,9 +691,15 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
592
691
|
}
|
|
593
692
|
// Set the bitrate for a specific camera channel.
|
|
594
693
|
async setBitrate(channelId, value) {
|
|
595
|
-
var _a;
|
|
596
|
-
// If we've disabled the ability to set
|
|
597
|
-
|
|
694
|
+
var _a, _b, _c;
|
|
695
|
+
// If we've disabled the ability to set the bitrate dynamically, silently fail. We prioritize switches over the global
|
|
696
|
+
// setting here, in case the user enabled both, using the principle that the most specific setting always wins. If the
|
|
697
|
+
// user has both the global setting and the switch enabled, the switch setting will take precedence.
|
|
698
|
+
if ((!this.accessory.context.dynamicBitrate &&
|
|
699
|
+
!((_a = this.nvr) === null || _a === void 0 ? void 0 : _a.optionEnabled(this.accessory.context.device, "Video.DynamicBitrate", false))) ||
|
|
700
|
+
(!this.accessory.context.dynamicBitrate &&
|
|
701
|
+
((_b = this.nvr) === null || _b === void 0 ? void 0 : _b.optionEnabled(this.accessory.context.device, "Video.DynamicBitrate", false)) &&
|
|
702
|
+
((_c = this.nvr) === null || _c === void 0 ? void 0 : _c.optionEnabled(this.accessory.context.device, "Video.DynamicBitrate.Switch", false)))) {
|
|
598
703
|
return true;
|
|
599
704
|
}
|
|
600
705
|
// Grab the device JSON.
|
|
@@ -621,6 +726,77 @@ class ProtectCamera extends protect_accessory_1.ProtectAccessory {
|
|
|
621
726
|
this.accessory.context.device = newDevice;
|
|
622
727
|
return true;
|
|
623
728
|
}
|
|
729
|
+
// Find an RTSP configuration for a given target resolution.
|
|
730
|
+
findRtspEntry(width, height, camera, address, rtspEntries, defaultStream = this.rtspQuality.StreamingDefault) {
|
|
731
|
+
var _a, _b;
|
|
732
|
+
// No RTSP entries to choose from, we're done.
|
|
733
|
+
if (!rtspEntries || !rtspEntries.length) {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
// First, we check to see if we've set an explicit preference for the target address.
|
|
737
|
+
if (camera && address) {
|
|
738
|
+
// If we don't have this address cached, look it up and cache it.
|
|
739
|
+
if (!this.rtspQuality[address]) {
|
|
740
|
+
// Check to see if there's an explicit preference set and cache the result.
|
|
741
|
+
if (this.nvr.optionEnabled(camera, "Video.Stream.Only.Low", false, address, true)) {
|
|
742
|
+
this.rtspQuality[address] = "LOW";
|
|
743
|
+
}
|
|
744
|
+
else if (this.nvr.optionEnabled(camera, "Video.Stream.Only.Medium", false, address, true)) {
|
|
745
|
+
this.rtspQuality[address] = "MEDIUM";
|
|
746
|
+
}
|
|
747
|
+
else if (this.nvr.optionEnabled(camera, "Video.Stream.Only.High", false, address, true)) {
|
|
748
|
+
this.rtspQuality[address] = "HIGH";
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
this.rtspQuality[address] = "None";
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
// If it's set to none, we default to our normal lookup logic.
|
|
755
|
+
if (this.rtspQuality[address] !== "None") {
|
|
756
|
+
return (_a = rtspEntries.find(x => x.channel.name.toUpperCase() === this.rtspQuality[address])) !== null && _a !== void 0 ? _a : null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Second, we check to see if we've set an explicit preference for stream quality.
|
|
760
|
+
if (defaultStream) {
|
|
761
|
+
return (_b = rtspEntries.find(x => x.channel.name.toUpperCase() === defaultStream)) !== null && _b !== void 0 ? _b : null;
|
|
762
|
+
}
|
|
763
|
+
// See if we have a match for our desired resolution on the camera. We ignore FPS - HomeKit clients seem
|
|
764
|
+
// to be able to handle it just fine.
|
|
765
|
+
const exactRtsp = rtspEntries.find(x => (x.resolution[0] === width) && (x.resolution[1] === height));
|
|
766
|
+
if (exactRtsp) {
|
|
767
|
+
return exactRtsp;
|
|
768
|
+
}
|
|
769
|
+
// No match found, let's see what we have that's closest. We try to be a bit smart about how we select our
|
|
770
|
+
// stream - if it's an HD quality stream request (720p+), we want to try to return something that's HD quality
|
|
771
|
+
// before looking for something lower resolution.
|
|
772
|
+
if ((width >= 1280) && (height >= 720)) {
|
|
773
|
+
for (const entry of rtspEntries) {
|
|
774
|
+
// Make sure we're looking at an HD resolution.
|
|
775
|
+
if (entry.resolution[0] < 1280) {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
// Return the first one we find.
|
|
779
|
+
return entry;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
// If we didn't request an HD resolution, or we couldn't find anything HD to use, we try to find whatever we
|
|
783
|
+
// can find that's close.
|
|
784
|
+
for (const entry of rtspEntries) {
|
|
785
|
+
if (width >= entry.resolution[0]) {
|
|
786
|
+
return entry;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// We couldn't find a close match, return the lowest resolution we found.
|
|
790
|
+
return rtspEntries[rtspEntries.length - 1];
|
|
791
|
+
}
|
|
792
|
+
// Find a streaming RTSP configuration for a given target resolution.
|
|
793
|
+
findRtsp(width, height, camera = null, address = "", rtspEntries = this.rtspEntries) {
|
|
794
|
+
return this.findRtspEntry(width, height, camera, address, rtspEntries);
|
|
795
|
+
}
|
|
796
|
+
// Find a recording RTSP configuration for a given target resolution.
|
|
797
|
+
findRecordingRtsp(width, height, camera = null, rtspEntries = this.rtspEntries) {
|
|
798
|
+
return this.findRtspEntry(width, height, camera, "", rtspEntries, this.rtspQuality.RecordingDefault);
|
|
799
|
+
}
|
|
624
800
|
// Utility function for sorting by resolution.
|
|
625
801
|
sortByResolutions(a, b) {
|
|
626
802
|
// Check width.
|