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/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 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.cameraOfflineImage = fs.readFileSync(imageFile);
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.cameraVideoOffImage = fs.readFileSync(imageFile);
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.createCameraServices();
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.cameraVideoOffImage !== undefined) {
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.cameraVideoOffImage;
600
+ imageBuffer = this.#cameraVideoOffImage;
479
601
  }
480
602
 
481
- if (this.deviceData.online === false && this.cameraOfflineImage !== undefined) {
603
+ if (this.deviceData.online === false && this.#cameraOfflineImage !== undefined) {
482
604
  // Return 'camera offline' jpg to image buffer
483
- imageBuffer = this.cameraOfflineImage;
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
- // Drop ip module by using small snippet of code below
536
- // Convert ipv4 mapped into ipv6 address into pure ipv4
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 (this.deviceData?.ffmpeg?.path === undefined && request.type === this.hap.StreamRequestTypes.START) {
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 appropirate streamer
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 === true ? 0 : 1,
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
- // 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
- }
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 (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);
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: 0,
1093
+ status: 1,
985
1094
  });
986
1095
  }
987
1096
  }
988
1097
  });
989
1098
 
990
- this.motionTimer = undefined; // No motion timer active
991
- }, this.deviceData.motionCooldown * 1000);
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
- // 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
- }
1117
+ this.motionTimer = undefined; // No motion timer active
1118
+ }, this.deviceData.motionCooldown * 1000);
1119
+ }
1003
1120
 
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);
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
- 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');
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: undefined,
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 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