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