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/dist/camera.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Nest Cameras
2
2
  // Part of homebridge-nest-accfactory
3
3
  //
4
- // Code version 7/9/2024
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.cameraOfflineImage = fs.readFileSync(imageFile);
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.cameraVideoOffImage = fs.readFileSync(imageFile);
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.createCameraServices();
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.cameraVideoOffImage !== undefined) {
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.cameraVideoOffImage;
599
+ imageBuffer = this.#cameraVideoOffImage;
479
600
  }
480
601
 
481
- if (this.deviceData.online === false && this.cameraOfflineImage !== undefined) {
602
+ if (this.deviceData.online === false && this.#cameraOfflineImage !== undefined) {
482
603
  // Return 'camera offline' jpg to image buffer
483
- imageBuffer = this.cameraOfflineImage;
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
- // Drop ip module by using small snippet of code below
536
- // Convert ipv4 mapped into ipv6 address into pure ipv4
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 (this.deviceData?.ffmpeg?.path === undefined && request.type === this.hap.StreamRequestTypes.START) {
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 appropirate streamer
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 === true ? 0 : 1,
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
- // Handle motion event
948
- // For a HKSV enabled camera, we will use this to trigger the starting of the HKSV recording if the camera is active
949
- if (event.types.includes('motion') === true) {
950
- if (this.motionTimer === undefined && (this.deviceData.hksv === false || this.streamer === undefined)) {
951
- this?.log?.info && this.log.info('Motion detected at "%s"', this.deviceData.description);
952
- }
953
-
954
- event.zone_ids.forEach((zoneID) => {
955
- if (
956
- typeof this.motionServices?.[zoneID]?.service === 'object' &&
957
- this.motionServices[zoneID].service.getCharacteristic(this.hap.Characteristic.MotionDetected).value !== true
958
- ) {
959
- // Trigger motion for matching zone of not aleady active
960
- this.motionServices[zoneID].service.updateCharacteristic(this.hap.Characteristic.MotionDetected, true);
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 (typeof this.motionServices?.[zoneID]?.service === 'object') {
977
- // Mark associted motion services as motion not detected
978
- this.motionServices[zoneID].service.updateCharacteristic(this.hap.Characteristic.MotionDetected, false);
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: 0,
1092
+ status: 1,
985
1093
  });
986
1094
  }
987
1095
  }
988
1096
  });
989
1097
 
990
- this.motionTimer = undefined; // No motion timer active
991
- }, this.deviceData.motionCooldown * 1000);
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
- // Handle person/face event
995
- // We also treat a 'face' event the same as a person event ie: if you have a face, you have a person
996
- if (event.types.includes('person') === true || event.types.includes('face') === true) {
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
- // Cooldown for person being detected
1005
- // Start this before we process further
1006
- this.personTimer = setTimeout(() => {
1007
- this.personTimer = undefined; // No person timer active
1008
- }, this.deviceData.personCooldown * 1000);
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
- if (event.types.includes('motion') === false) {
1011
- // If person/face events doesn't include a motion event, add in here
1012
- // This will handle all the motion triggering stuff
1013
- event.types.push('motion');
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: undefined,
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 6/9/2024
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({ 'doorbell.indoor_chime.enabled': value });
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', this.deviceData.description);
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"', this.deviceData.description);
104
+ this?.log?.info && this.log.info('Doorbell rung at "%s"', deviceData.description);
105
105
  this.controller.ringDoorbell();
106
106
  }
107
107