homebridge-nest-accfactory 0.0.4-a → 0.0.5
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/CHANGELOG.md +8 -1
- package/README.md +7 -7
- package/dist/HomeKitDevice.js +21 -10
- package/dist/HomeKitHistory.js +2 -24
- package/dist/camera.js +224 -236
- package/dist/doorbell.js +4 -4
- package/dist/floodlight.js +97 -0
- package/dist/index.js +7 -7
- package/dist/nexustalk.js +219 -418
- package/dist/protect.js +8 -9
- package/dist/protobuf/google/trait/product/camera.proto +1 -0
- package/dist/protobuf/googlehome/foyer.proto +11 -3
- package/dist/protobuf/nest/nexustalk.proto +181 -0
- package/dist/protobuf/nestlabs/eventingapi/v1.proto +6 -2
- package/dist/protobuf/nestlabs/gateway/v1.proto +29 -23
- package/dist/protobuf/nestlabs/gateway/v2.proto +16 -8
- package/dist/protobuf/root.proto +2 -27
- package/dist/protobuf/weave/trait/actuator.proto +13 -0
- package/dist/streamer.js +33 -30
- package/dist/system.js +1105 -1095
- package/dist/thermostat.js +5 -6
- package/package.json +7 -6
- package/dist/protobuf/nest/messages.proto +0 -8
- package/dist/webrtc.js +0 -55
package/dist/camera.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Nest Cameras
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version
|
|
4
|
+
// Code version 12/9/2024
|
|
5
5
|
// Mark Hulskamp
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
@@ -37,14 +37,14 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
37
37
|
personTimer = undefined; // Cooldown timer for person/face events
|
|
38
38
|
motionTimer = undefined; // Cooldown timer for motion events
|
|
39
39
|
snapshotTimer = undefined; // Timer for cached snapshot images
|
|
40
|
-
cameraOfflineImage = undefined; // JPG image buffer for camera offline
|
|
41
|
-
cameraVideoOffImage = undefined; // JPG image buffer for camera video off
|
|
42
40
|
lastSnapshotImage = undefined; // JPG image buffer for last camera snapshot
|
|
43
41
|
snapshotEvent = undefined; // Event for which to get snapshot for
|
|
44
42
|
|
|
45
43
|
// Internal data only for this class
|
|
46
44
|
#hkSessions = []; // Track live and recording active sessions
|
|
47
45
|
#recordingConfig = {}; // HomeKit Secure Video recording configuration
|
|
46
|
+
#cameraOfflineImage = undefined; // JPG image buffer for camera offline
|
|
47
|
+
#cameraVideoOffImage = undefined; // JPG image buffer for camera video off
|
|
48
48
|
|
|
49
49
|
constructor(accessory, api, log, eventEmitter, deviceData) {
|
|
50
50
|
super(accessory, api, log, eventEmitter, deviceData);
|
|
@@ -52,16 +52,14 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
52
52
|
// buffer for camera offline jpg image
|
|
53
53
|
let imageFile = path.resolve(__dirname + '/res/' + CAMERAOFFLINEJPGFILE);
|
|
54
54
|
if (fs.existsSync(imageFile) === true) {
|
|
55
|
-
this
|
|
55
|
+
this.#cameraOfflineImage = fs.readFileSync(imageFile);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// buffer for camera stream off jpg image
|
|
59
59
|
imageFile = path.resolve(__dirname + '/res/' + CAMERAOFFJPGFILE);
|
|
60
60
|
if (fs.existsSync(imageFile) === true) {
|
|
61
|
-
this
|
|
61
|
+
this.#cameraVideoOffImage = fs.readFileSync(imageFile);
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
this.set({ 'watermark.enabled': false }); // 'Try' to turn off Nest watermark in video stream
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
// Class functions
|
|
@@ -78,7 +76,128 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
// Setup additional services/characteristics after we have a controller created
|
|
81
|
-
this.
|
|
79
|
+
this.operatingModeService = this.controller?.recordingManagement?.operatingModeService;
|
|
80
|
+
if (this.operatingModeService === undefined) {
|
|
81
|
+
// Add in operating mode service for a non-hksv camera/doorbell
|
|
82
|
+
// Allow us to change things such as night vision, camera indicator etc within HomeKit for those also:-)
|
|
83
|
+
this.operatingModeService = this.accessory.getService(this.hap.Service.CameraOperatingMode);
|
|
84
|
+
if (this.operatingModeService === undefined) {
|
|
85
|
+
this.operatingModeService = this.accessory.addService(this.hap.Service.CameraOperatingMode, '', 1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Setup set callbacks for characteristics
|
|
90
|
+
if (this.operatingModeService !== undefined) {
|
|
91
|
+
if (this.deviceData.has_statusled === true) {
|
|
92
|
+
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator) === false) {
|
|
93
|
+
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator);
|
|
94
|
+
}
|
|
95
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator).onSet((value) => {
|
|
96
|
+
// 0 = auto, 1 = low, 2 = high
|
|
97
|
+
// We'll use auto mode for led on and low for led off
|
|
98
|
+
if (
|
|
99
|
+
(value === true && this.deviceData.statusled_brightness !== 0) ||
|
|
100
|
+
(value === false && this.deviceData.statusled_brightness !== 1)
|
|
101
|
+
) {
|
|
102
|
+
this.set({ statusled_brightness: value === true ? 0 : 1 });
|
|
103
|
+
if (this?.log?.info) {
|
|
104
|
+
this.log.info('Recording status LED on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator).onGet(() => {
|
|
110
|
+
return this.deviceData.statusled_brightness !== 1;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (this.deviceData.has_irled === true) {
|
|
115
|
+
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.NightVision) === false) {
|
|
116
|
+
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.NightVision);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.NightVision).onSet((value) => {
|
|
120
|
+
// only change IRLed status value if different than on-device
|
|
121
|
+
if ((value === false && this.deviceData.irled_enabled === true) || (value === true && this.deviceData.irled_enabled === false)) {
|
|
122
|
+
this.set({ irled_enabled: value === true ? 'auto_on' : 'always_off' });
|
|
123
|
+
|
|
124
|
+
if (this?.log?.info) {
|
|
125
|
+
this.log.info('Night vision on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.NightVision).onGet(() => {
|
|
131
|
+
return this.deviceData.irled_enabled;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.ManuallyDisabled) === false) {
|
|
136
|
+
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.ManuallyDisabled);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.ManuallyDisabled).onSet((value) => {
|
|
140
|
+
if (value !== this.operatingModeService.getCharacteristic(this.hap.Characteristic.ManuallyDisabled).value) {
|
|
141
|
+
// Make sure only updating status if HomeKit value *actually changes*
|
|
142
|
+
if (
|
|
143
|
+
(this.deviceData.streaming_enabled === false && value === false) ||
|
|
144
|
+
(this.deviceData.streaming_enabled === true && value === true)
|
|
145
|
+
) {
|
|
146
|
+
// Camera state does not reflect requested state, so fix
|
|
147
|
+
this.set({ streaming_enabled: value === false ? true : false });
|
|
148
|
+
if (this?.log?.info) {
|
|
149
|
+
this.log.info('Camera on "%s" was turned', this.deviceData.description, value === false ? 'on' : 'off');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.ManuallyDisabled).onGet(() => {
|
|
156
|
+
return this.deviceData.streaming_enabled === false
|
|
157
|
+
? this.hap.Characteristic.ManuallyDisabled.DISABLED
|
|
158
|
+
: this.hap.Characteristic.ManuallyDisabled.ENABLED;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (this.deviceData.has_video_flip === true) {
|
|
162
|
+
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.ImageRotation) === false) {
|
|
163
|
+
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.ImageRotation);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.ImageRotation).onGet(() => {
|
|
167
|
+
return this.deviceData.video_flipped === true ? 180 : 0;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (this.controller?.recordingManagement?.recordingManagementService !== undefined) {
|
|
173
|
+
if (this.deviceData.has_microphone === true) {
|
|
174
|
+
this.controller.recordingManagement.recordingManagementService
|
|
175
|
+
.getCharacteristic(this.hap.Characteristic.RecordingAudioActive)
|
|
176
|
+
.onSet((value) => {
|
|
177
|
+
if (
|
|
178
|
+
(this.deviceData.audio_enabled === true && value === this.hap.Characteristic.RecordingAudioActive.DISABLE) ||
|
|
179
|
+
(this.deviceData.audio_enabled === false && value === this.hap.Characteristic.RecordingAudioActive.ENABLE)
|
|
180
|
+
) {
|
|
181
|
+
this.set({ audio_enabled: value === this.hap.Characteristic.RecordingAudioActive.ENABLE ? true : false });
|
|
182
|
+
if (this?.log?.info) {
|
|
183
|
+
this.log.info(
|
|
184
|
+
'Audio recording on "%s" was turned',
|
|
185
|
+
this.deviceData.description,
|
|
186
|
+
value === this.hap.Characteristic.RecordingAudioActive.ENABLE ? 'on' : 'off',
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
this.controller.recordingManagement.recordingManagementService
|
|
193
|
+
.getCharacteristic(this.hap.Characteristic.RecordingAudioActive)
|
|
194
|
+
.onGet(() => {
|
|
195
|
+
return this.deviceData.audio_enabled === true
|
|
196
|
+
? this.hap.Characteristic.RecordingAudioActive.ENABLE
|
|
197
|
+
: this.hap.Characteristic.RecordingAudioActive.DISABLE;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
82
201
|
|
|
83
202
|
// Depending on the streaming profiles that the camera supports, this will be either nexustalk or webrtc
|
|
84
203
|
// We'll also start pre-buffering if required for HKSV
|
|
@@ -344,12 +463,14 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
344
463
|
});
|
|
345
464
|
|
|
346
465
|
// ffmpeg outputs to stderr
|
|
466
|
+
/*
|
|
347
467
|
this.#hkSessions[sessionID].ffmpeg.stderr.on('data', (data) => {
|
|
348
468
|
if (data.toString().includes('frame=') === false) {
|
|
349
469
|
// Monitor ffmpeg output while testing. Use 'ffmpeg as a debug option'
|
|
350
470
|
this?.log?.debug && this.log.debug(data.toString());
|
|
351
471
|
}
|
|
352
472
|
});
|
|
473
|
+
*/
|
|
353
474
|
|
|
354
475
|
this.streamer !== undefined &&
|
|
355
476
|
this.streamer.startRecordStream(
|
|
@@ -473,14 +594,14 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
473
594
|
}
|
|
474
595
|
}
|
|
475
596
|
|
|
476
|
-
if (this.deviceData.streaming_enabled === false && this.deviceData.online === true && this
|
|
597
|
+
if (this.deviceData.streaming_enabled === false && this.deviceData.online === true && this.#cameraVideoOffImage !== undefined) {
|
|
477
598
|
// Return 'camera switched off' jpg to image buffer
|
|
478
|
-
imageBuffer = this
|
|
599
|
+
imageBuffer = this.#cameraVideoOffImage;
|
|
479
600
|
}
|
|
480
601
|
|
|
481
|
-
if (this.deviceData.online === false && this
|
|
602
|
+
if (this.deviceData.online === false && this.#cameraOfflineImage !== undefined) {
|
|
482
603
|
// Return 'camera offline' jpg to image buffer
|
|
483
|
-
imageBuffer = this
|
|
604
|
+
imageBuffer = this.#cameraOfflineImage;
|
|
484
605
|
}
|
|
485
606
|
|
|
486
607
|
if (imageBuffer === undefined) {
|
|
@@ -532,8 +653,8 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
532
653
|
|
|
533
654
|
// Build response back to HomeKit with the details filled out
|
|
534
655
|
|
|
535
|
-
//
|
|
536
|
-
//
|
|
656
|
+
// Dropped ip module by using small snippet of code below
|
|
657
|
+
// Converts ipv4 mapped into ipv6 address into pure ipv4
|
|
537
658
|
if (request.addressVersion === 'ipv4' && request.sourceAddress.startsWith('::ffff:') === true) {
|
|
538
659
|
request.sourceAddress = request.sourceAddress.replace('::ffff:', '');
|
|
539
660
|
}
|
|
@@ -558,34 +679,26 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
558
679
|
}
|
|
559
680
|
|
|
560
681
|
async handleStreamRequest(request, callback) {
|
|
561
|
-
// called when HomeKit asks to start/stop/reconfigure a camera/doorbell stream
|
|
562
|
-
if (this.streamer === undefined) {
|
|
682
|
+
// called when HomeKit asks to start/stop/reconfigure a camera/doorbell live stream
|
|
683
|
+
if (request.type === this.hap.StreamRequestTypes.START && this.streamer === undefined) {
|
|
684
|
+
// We have no streamer object configured, so cannot do live streams!!
|
|
563
685
|
this?.log?.error &&
|
|
564
686
|
this.log.error(
|
|
565
687
|
'Received request to start live video for "%s" however we do not any associated streaming protocol supported',
|
|
566
688
|
this.deviceData.description,
|
|
567
689
|
);
|
|
568
|
-
|
|
569
|
-
if (typeof callback === 'function') {
|
|
570
|
-
callback(); // do callback if defined
|
|
571
|
-
}
|
|
572
|
-
return;
|
|
573
690
|
}
|
|
574
691
|
|
|
575
|
-
if (
|
|
692
|
+
if (request.type === this.hap.StreamRequestTypes.START && this.deviceData?.ffmpeg?.path === undefined) {
|
|
693
|
+
// No ffmpeg binary present, so cannot do live streams!!
|
|
576
694
|
this?.log?.warn &&
|
|
577
695
|
this.log.warn(
|
|
578
696
|
'Received request to start live video for "%s" however we do not have an ffmpeg binary present',
|
|
579
697
|
this.deviceData.description,
|
|
580
698
|
);
|
|
581
|
-
|
|
582
|
-
if (typeof callback === 'function') {
|
|
583
|
-
callback(); // do callback if defined
|
|
584
|
-
}
|
|
585
|
-
return;
|
|
586
699
|
}
|
|
587
700
|
|
|
588
|
-
if (request.type === this.hap.StreamRequestTypes.START) {
|
|
701
|
+
if (request.type === this.hap.StreamRequestTypes.START && this.streamer !== undefined && this.deviceData?.ffmpeg?.path !== undefined) {
|
|
589
702
|
// Build our ffmpeg command string for the liveview video/audio stream
|
|
590
703
|
let commandLine =
|
|
591
704
|
'-hide_banner -nostats' +
|
|
@@ -665,12 +778,14 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
665
778
|
}); // Extra pipe, #3 for audio data
|
|
666
779
|
|
|
667
780
|
// ffmpeg console output is via stderr
|
|
781
|
+
/*
|
|
668
782
|
ffmpegStreaming.stderr.on('data', (data) => {
|
|
669
783
|
if (data.toString().includes('frame=') === false) {
|
|
670
784
|
// Monitor ffmpeg output while testing. Use 'ffmpeg as a debug option'
|
|
671
785
|
this?.log?.debug && this.log.debug(data.toString());
|
|
672
786
|
}
|
|
673
787
|
});
|
|
788
|
+
*/
|
|
674
789
|
|
|
675
790
|
ffmpegStreaming.on('exit', (code, signal) => {
|
|
676
791
|
if (signal !== 'SIGKILL' || signal === null) {
|
|
@@ -759,9 +874,11 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
759
874
|
});
|
|
760
875
|
|
|
761
876
|
// ffmpeg console output is via stderr
|
|
877
|
+
/*
|
|
762
878
|
ffmpegAudioTalkback.stderr.on('data', (data) => {
|
|
763
879
|
this?.log?.debug && this.log.debug(data.toString());
|
|
764
880
|
});
|
|
881
|
+
*/
|
|
765
882
|
|
|
766
883
|
// Write out SDP configuration
|
|
767
884
|
// Tried to align the SDP configuration to what HomeKit has sent us in its audio request details
|
|
@@ -813,7 +930,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
813
930
|
ffmpegAudioTalkback?.stdout ? 'with two-way audio' : '',
|
|
814
931
|
);
|
|
815
932
|
|
|
816
|
-
// Start the
|
|
933
|
+
// Start the appropriate streamer
|
|
817
934
|
this.streamer !== undefined &&
|
|
818
935
|
this.streamer.startLiveStream(
|
|
819
936
|
request.sessionID,
|
|
@@ -883,11 +1000,11 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
883
1000
|
|
|
884
1001
|
if (this.operatingModeService !== undefined) {
|
|
885
1002
|
// Update camera off/on status
|
|
886
|
-
// 0 = Enabled
|
|
887
|
-
// 1 = Disabled
|
|
888
1003
|
this.operatingModeService.updateCharacteristic(
|
|
889
1004
|
this.hap.Characteristic.ManuallyDisabled,
|
|
890
|
-
deviceData.streaming_enabled ===
|
|
1005
|
+
deviceData.streaming_enabled === false
|
|
1006
|
+
? this.hap.Characteristic.ManuallyDisabled.DISABLED
|
|
1007
|
+
: this.hap.Characteristic.ManuallyDisabled.ENABLED,
|
|
891
1008
|
);
|
|
892
1009
|
|
|
893
1010
|
if (deviceData.has_statusled === true && typeof deviceData.statusled_brightness === 'number') {
|
|
@@ -944,73 +1061,83 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
944
1061
|
// For HKSV, we're interested motion events
|
|
945
1062
|
// For non-HKSV, we're interested motion, face and person events (maybe sound and package later)
|
|
946
1063
|
deviceData.alerts.forEach((event) => {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
this.
|
|
961
|
-
|
|
962
|
-
// Log motion started into history
|
|
963
|
-
if (typeof this.historyService?.addHistory === 'function') {
|
|
964
|
-
this.historyService.addHistory(this.motionServices[zoneID].service, {
|
|
965
|
-
time: Math.floor(Date.now() / 1000),
|
|
966
|
-
status: 1,
|
|
967
|
-
});
|
|
968
|
-
}
|
|
1064
|
+
if (
|
|
1065
|
+
this.operatingModeService === undefined ||
|
|
1066
|
+
(this.operatingModeService !== undefined &&
|
|
1067
|
+
this.operatingModeService.getCharacteristic(this.hap.Characteristic.HomeKitCameraActive).value ===
|
|
1068
|
+
this.hap.Characteristic.HomeKitCameraActive.ON)
|
|
1069
|
+
) {
|
|
1070
|
+
// We're configured to handle camera events
|
|
1071
|
+
// https://github.com/Supereg/secure-video-specification?tab=readme-ov-file#33-homekitcameraactive
|
|
1072
|
+
|
|
1073
|
+
// Handle motion event
|
|
1074
|
+
// For a HKSV enabled camera, we will use this to trigger the starting of the HKSV recording if the camera is active
|
|
1075
|
+
if (event.types.includes('motion') === true) {
|
|
1076
|
+
if (this.motionTimer === undefined && (this.deviceData.hksv === false || this.streamer === undefined)) {
|
|
1077
|
+
this?.log?.info && this.log.info('Motion detected at "%s"', deviceData.description);
|
|
969
1078
|
}
|
|
970
|
-
});
|
|
971
1079
|
|
|
972
|
-
// Clear any motion active timer so we can extend if more motion detected
|
|
973
|
-
clearTimeout(this.motionTimer);
|
|
974
|
-
this.motionTimer = setTimeout(() => {
|
|
975
1080
|
event.zone_ids.forEach((zoneID) => {
|
|
976
|
-
if (
|
|
977
|
-
|
|
978
|
-
this.motionServices[zoneID].service.
|
|
1081
|
+
if (
|
|
1082
|
+
typeof this.motionServices?.[zoneID]?.service === 'object' &&
|
|
1083
|
+
this.motionServices[zoneID].service.getCharacteristic(this.hap.Characteristic.MotionDetected).value !== true
|
|
1084
|
+
) {
|
|
1085
|
+
// Trigger motion for matching zone of not aleady active
|
|
1086
|
+
this.motionServices[zoneID].service.updateCharacteristic(this.hap.Characteristic.MotionDetected, true);
|
|
979
1087
|
|
|
980
1088
|
// Log motion started into history
|
|
981
1089
|
if (typeof this.historyService?.addHistory === 'function') {
|
|
982
1090
|
this.historyService.addHistory(this.motionServices[zoneID].service, {
|
|
983
1091
|
time: Math.floor(Date.now() / 1000),
|
|
984
|
-
status:
|
|
1092
|
+
status: 1,
|
|
985
1093
|
});
|
|
986
1094
|
}
|
|
987
1095
|
}
|
|
988
1096
|
});
|
|
989
1097
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1098
|
+
// Clear any motion active timer so we can extend if more motion detected
|
|
1099
|
+
clearTimeout(this.motionTimer);
|
|
1100
|
+
this.motionTimer = setTimeout(() => {
|
|
1101
|
+
event.zone_ids.forEach((zoneID) => {
|
|
1102
|
+
if (typeof this.motionServices?.[zoneID]?.service === 'object') {
|
|
1103
|
+
// Mark associted motion services as motion not detected
|
|
1104
|
+
this.motionServices[zoneID].service.updateCharacteristic(this.hap.Characteristic.MotionDetected, false);
|
|
1105
|
+
|
|
1106
|
+
// Log motion started into history
|
|
1107
|
+
if (typeof this.historyService?.addHistory === 'function') {
|
|
1108
|
+
this.historyService.addHistory(this.motionServices[zoneID].service, {
|
|
1109
|
+
time: Math.floor(Date.now() / 1000),
|
|
1110
|
+
status: 0,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
993
1115
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
if (this.personTimer === undefined) {
|
|
998
|
-
// We don't have a person cooldown timer running, so we can process the 'person'/'face' event
|
|
999
|
-
if (this?.log?.info && (this.deviceData.hksv === false || this.streamer === undefined)) {
|
|
1000
|
-
// We'll only log a person detected event if HKSV is disabled
|
|
1001
|
-
this.log.info('Person detected at "%s"', this.deviceData.description);
|
|
1002
|
-
}
|
|
1116
|
+
this.motionTimer = undefined; // No motion timer active
|
|
1117
|
+
}, this.deviceData.motionCooldown * 1000);
|
|
1118
|
+
}
|
|
1003
1119
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1120
|
+
// Handle person/face event
|
|
1121
|
+
// We also treat a 'face' event the same as a person event ie: if you have a face, you have a person
|
|
1122
|
+
if (event.types.includes('person') === true || event.types.includes('face') === true) {
|
|
1123
|
+
if (this.personTimer === undefined) {
|
|
1124
|
+
// We don't have a person cooldown timer running, so we can process the 'person'/'face' event
|
|
1125
|
+
if (this?.log?.info && (this.deviceData.hksv === false || this.streamer === undefined)) {
|
|
1126
|
+
// We'll only log a person detected event if HKSV is disabled
|
|
1127
|
+
this.log.info('Person detected at "%s"', deviceData.description);
|
|
1128
|
+
}
|
|
1009
1129
|
|
|
1010
|
-
|
|
1011
|
-
//
|
|
1012
|
-
|
|
1013
|
-
|
|
1130
|
+
// Cooldown for person being detected
|
|
1131
|
+
// Start this before we process further
|
|
1132
|
+
this.personTimer = setTimeout(() => {
|
|
1133
|
+
this.personTimer = undefined; // No person timer active
|
|
1134
|
+
}, this.deviceData.personCooldown * 1000);
|
|
1135
|
+
|
|
1136
|
+
if (event.types.includes('motion') === false) {
|
|
1137
|
+
// If person/face events doesn't include a motion event, add in here
|
|
1138
|
+
// This will handle all the motion triggering stuff
|
|
1139
|
+
event.types.push('motion');
|
|
1140
|
+
}
|
|
1014
1141
|
}
|
|
1015
1142
|
}
|
|
1016
1143
|
}
|
|
@@ -1044,143 +1171,6 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1044
1171
|
}
|
|
1045
1172
|
}
|
|
1046
1173
|
|
|
1047
|
-
createCameraServices() {
|
|
1048
|
-
if (this.controller === undefined) {
|
|
1049
|
-
return;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
this.operatingModeService = this.controller?.recordingManagement?.operatingModeService;
|
|
1053
|
-
if (this.operatingModeService === undefined) {
|
|
1054
|
-
// Add in operating mode service for a non-hksv camera/doorbell
|
|
1055
|
-
// Allow us to change things such as night vision, camera indicator etc within HomeKit for those also:-)
|
|
1056
|
-
this.operatingModeService = this.accessory.getService(this.hap.Service.CameraOperatingMode);
|
|
1057
|
-
if (this.operatingModeService === undefined) {
|
|
1058
|
-
this.operatingModeService = this.accessory.addService(this.hap.Service.CameraOperatingMode, '', 1);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// Setup set callbacks for characteristics
|
|
1063
|
-
if (this.deviceData.has_statusled === true && this.operatingModeService !== undefined) {
|
|
1064
|
-
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator) === false) {
|
|
1065
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator);
|
|
1066
|
-
}
|
|
1067
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator).onSet((value) => {
|
|
1068
|
-
// 0 = auto, 1 = low, 2 = high
|
|
1069
|
-
// We'll use auto mode for led on and low for led off
|
|
1070
|
-
if (
|
|
1071
|
-
(value === true && this.deviceData.statusled_brightness !== 0) ||
|
|
1072
|
-
(value === false && this.deviceData.statusled_brightness !== 1)
|
|
1073
|
-
) {
|
|
1074
|
-
this.set({ 'statusled.brightness': value === true ? 0 : 1 });
|
|
1075
|
-
if (this?.log?.info) {
|
|
1076
|
-
this.log.info('Recording status LED on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1081
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator).onGet(() => {
|
|
1082
|
-
return this.deviceData.statusled_brightness !== 1;
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
if (this.deviceData.has_irled === true && this.operatingModeService !== undefined) {
|
|
1087
|
-
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.NightVision) === false) {
|
|
1088
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.NightVision);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.NightVision).onSet((value) => {
|
|
1092
|
-
// only change IRLed status value if different than on-device
|
|
1093
|
-
if ((value === false && this.deviceData.irled_enabled === true) || (value === true && this.deviceData.irled_enabled === false)) {
|
|
1094
|
-
this.set({ 'irled.state': value === true ? 'auto_on' : 'always_off' });
|
|
1095
|
-
|
|
1096
|
-
if (this?.log?.info) {
|
|
1097
|
-
this.log.info('Night vision on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
});
|
|
1101
|
-
|
|
1102
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.NightVision).onGet(() => {
|
|
1103
|
-
return this.deviceData.irled_enabled;
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
if (this.operatingModeService !== undefined) {
|
|
1108
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.HomeKitCameraActive).onSet((value) => {
|
|
1109
|
-
if (value !== this.operatingModeService.getCharacteristic(this.hap.Characteristic.HomeKitCameraActive).value) {
|
|
1110
|
-
// Make sure only updating status if HomeKit value *actually changes*
|
|
1111
|
-
if (
|
|
1112
|
-
(this.deviceData.streaming_enabled === false && value === this.hap.Characteristic.HomeKitCameraActive.ON) ||
|
|
1113
|
-
(this.deviceData.streaming_enabled === true && value === this.hap.Characteristic.HomeKitCameraActive.OFF)
|
|
1114
|
-
) {
|
|
1115
|
-
// Camera state does not reflect requested state, so fix
|
|
1116
|
-
this.set({ 'streaming.enabled': value === this.hap.Characteristic.HomeKitCameraActive.ON ? true : false });
|
|
1117
|
-
if (this.log.info) {
|
|
1118
|
-
this.log.info(
|
|
1119
|
-
'Camera on "%s" was turned',
|
|
1120
|
-
this.deviceData.description,
|
|
1121
|
-
value === this.hap.Characteristic.HomeKitCameraActive.ON ? 'on' : 'off',
|
|
1122
|
-
);
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
});
|
|
1127
|
-
|
|
1128
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.HomeKitCameraActive).onGet(() => {
|
|
1129
|
-
return this.deviceData.streaming_enabled === true
|
|
1130
|
-
? this.hap.Characteristic.HomeKitCameraActive.ON
|
|
1131
|
-
: this.hap.Characteristic.HomeKitCameraActive.OFF;
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
if (this.deviceData.has_video_flip === true && this.operatingModeService !== undefined) {
|
|
1136
|
-
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.ImageRotation) === false) {
|
|
1137
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.ImageRotation);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.ImageRotation).onGet(() => {
|
|
1141
|
-
return this.deviceData.video_flipped === true ? 180 : 0;
|
|
1142
|
-
});
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (this.deviceData.has_irled === true && this.operatingModeService !== undefined) {
|
|
1146
|
-
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.ManuallyDisabled) === false) {
|
|
1147
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.ManuallyDisabled);
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.ManuallyDisabled).onGet(() => {
|
|
1151
|
-
return this.deviceData.streaming_enabled === true ? 0 : 1;
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if (this.deviceData.has_microphone === true && this.controller?.recordingManagement?.recordingManagementService !== undefined) {
|
|
1156
|
-
this.controller.recordingManagement.recordingManagementService
|
|
1157
|
-
.getCharacteristic(this.hap.Characteristic.RecordingAudioActive)
|
|
1158
|
-
.onSet((value) => {
|
|
1159
|
-
if (
|
|
1160
|
-
(this.deviceData.audio_enabled === true && value === this.hap.Characteristic.RecordingAudioActive.DISABLE) ||
|
|
1161
|
-
(this.deviceData.audio_enabled === false && value === this.hap.Characteristic.RecordingAudioActive.ENABLE)
|
|
1162
|
-
) {
|
|
1163
|
-
this.set({ 'audio.enabled': value === this.hap.Characteristic.RecordingAudioActive.ENABLE ? true : false });
|
|
1164
|
-
if (this?.log?.info) {
|
|
1165
|
-
this.log.info(
|
|
1166
|
-
'Audio recording on "%s" was turned',
|
|
1167
|
-
this.deviceData.description,
|
|
1168
|
-
value === this.hap.Characteristic.RecordingAudioActive.ENABLE ? 'on' : 'off',
|
|
1169
|
-
);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
this.controller.recordingManagement.recordingManagementService
|
|
1175
|
-
.getCharacteristic(this.hap.Characteristic.RecordingAudioActive)
|
|
1176
|
-
.onGet(() => {
|
|
1177
|
-
return this.deviceData.audio_enabled === true
|
|
1178
|
-
? this.hap.Characteristic.RecordingAudioActive.ENABLE
|
|
1179
|
-
: this.hap.Characteristic.RecordingAudioActive.DISABLE;
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
1174
|
generateControllerOptions() {
|
|
1185
1175
|
// Setup HomeKit controller camera/doorbell options
|
|
1186
1176
|
let controllerOptions = {
|
|
@@ -1213,27 +1203,25 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1213
1203
|
levels: [this.hap.H264Level.LEVEL3_1, this.hap.H264Level.LEVEL3_2, this.hap.H264Level.LEVEL4_0],
|
|
1214
1204
|
},
|
|
1215
1205
|
},
|
|
1216
|
-
audio:
|
|
1206
|
+
audio: {
|
|
1207
|
+
twoWayAudio:
|
|
1208
|
+
this.deviceData?.ffmpeg?.libfdk_aac === true &&
|
|
1209
|
+
this.deviceData?.ffmpeg?.libspeex === true &&
|
|
1210
|
+
this.deviceData.has_speaker === true &&
|
|
1211
|
+
this.deviceData.has_microphone === true,
|
|
1212
|
+
codecs: [
|
|
1213
|
+
{
|
|
1214
|
+
type: this.hap.AudioStreamingCodecType.AAC_ELD,
|
|
1215
|
+
samplerate: this.hap.AudioStreamingSamplerate.KHZ_16,
|
|
1216
|
+
audioChannel: 1,
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1219
|
+
},
|
|
1217
1220
|
},
|
|
1218
1221
|
recording: undefined,
|
|
1219
1222
|
sensors: undefined,
|
|
1220
1223
|
};
|
|
1221
1224
|
|
|
1222
|
-
if (this.deviceData?.ffmpeg?.libfdk_aac === true) {
|
|
1223
|
-
// Enabling audio for streaming if we have the appropriate codec in ffmpeg binary present
|
|
1224
|
-
controllerOptions.streamingOptions.audio = {
|
|
1225
|
-
twoWayAudio:
|
|
1226
|
-
this.deviceData?.ffmpeg?.libspeex === true && this.deviceData.has_speaker === true && this.deviceData.has_microphone === true,
|
|
1227
|
-
codecs: [
|
|
1228
|
-
{
|
|
1229
|
-
type: this.hap.AudioStreamingCodecType.AAC_ELD,
|
|
1230
|
-
samplerate: this.hap.AudioStreamingSamplerate.KHZ_16,
|
|
1231
|
-
audioChannel: 1,
|
|
1232
|
-
},
|
|
1233
|
-
],
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
1225
|
if (this.deviceData.hksv === true) {
|
|
1238
1226
|
controllerOptions.recording = {
|
|
1239
1227
|
delegate: this,
|
package/dist/doorbell.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Nest Doorbell(s)
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version
|
|
4
|
+
// Code version 12/9/2024
|
|
5
5
|
// Mark Hulskamp
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
@@ -41,7 +41,7 @@ export default class NestDoorbell extends NestCamera {
|
|
|
41
41
|
this.switchService.getCharacteristic(this.hap.Characteristic.On).onSet((value) => {
|
|
42
42
|
if (value !== this.deviceData.indoor_chime_enabled) {
|
|
43
43
|
// only change indoor chime status value if different than on-device
|
|
44
|
-
this.set({
|
|
44
|
+
this.set({ indoor_chime_enabled: value });
|
|
45
45
|
|
|
46
46
|
this?.log?.info && this.log.info('Indoor chime on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
47
47
|
}
|
|
@@ -97,11 +97,11 @@ export default class NestDoorbell extends NestCamera {
|
|
|
97
97
|
|
|
98
98
|
if (deviceData.indoor_chime_enabled === false || deviceData.quiet_time_enabled === true) {
|
|
99
99
|
// Indoor chime is disabled or quiet time is enabled, so we won't 'ring' the doorbell
|
|
100
|
-
this?.log?.warn && this.log.warn('Doorbell rung at "%s" but indoor chime is silenced',
|
|
100
|
+
this?.log?.warn && this.log.warn('Doorbell rung at "%s" but indoor chime is silenced', deviceData.description);
|
|
101
101
|
}
|
|
102
102
|
if (deviceData.indoor_chime_enabled === true && deviceData.quiet_time_enabled === false) {
|
|
103
103
|
// Indoor chime is enabled and quiet time isn't enabled, so 'ring' the doorbell
|
|
104
|
-
this?.log?.info && this.log.info('Doorbell rung at "%s"',
|
|
104
|
+
this?.log?.info && this.log.info('Doorbell rung at "%s"', deviceData.description);
|
|
105
105
|
this.controller.ringDoorbell();
|
|
106
106
|
}
|
|
107
107
|
|