homebridge-nest-accfactory 0.2.2 → 0.2.3

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/system.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Nest System communications
2
2
  // Part of homebridge-nest-accfactory
3
3
  //
4
- // Code version 3/10/2024
4
+ // Code version 2024/12/01
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -78,7 +78,7 @@ export default class NestAccfactory {
78
78
 
79
79
  // Perform validation on the configuration passed into us and set defaults if not present
80
80
 
81
- // Build our accounts connection object. Allows us to have multiple diffent accont connections under the one accessory
81
+ // Build our accounts connection object. Allows us to have multiple diffent account connections under the one accessory
82
82
  Object.keys(this.config).forEach((key) => {
83
83
  if (this.config[key]?.access_token !== undefined && this.config[key].access_token !== '') {
84
84
  // Nest account connection, assign a random UUID for each connection
@@ -105,7 +105,7 @@ export default class NestAccfactory {
105
105
  authorised: false,
106
106
  issuetoken: this.config[key].issuetoken,
107
107
  cookie: this.config[key].cookie,
108
- fieldTest: typeof this.config[key]?.fieldTest === 'boolean' ? this.config[key].fieldTest : false,
108
+ fieldTest: this.config[key]?.fieldTest === true,
109
109
  referer: this.config[key]?.fieldTest === true ? 'home.ft.nest.com' : 'home.nest.com',
110
110
  restAPIHost: this.config[key]?.fieldTest === true ? 'home.ft.nest.com' : 'home.nest.com',
111
111
  cameraAPIHost: this.config[key]?.fieldTest === true ? 'camera.home.ft.nest.com' : 'camera.home.nest.com',
@@ -124,10 +124,10 @@ export default class NestAccfactory {
124
124
  this.config.options = {};
125
125
  }
126
126
 
127
- this.config.options.eveHistory = typeof this.config.options?.eveHistory === 'boolean' ? this.config.options.eveHistory : false;
127
+ this.config.options.eveHistory = this.config.options?.eveHistory === true;
128
128
  this.config.options.elevation = isNaN(this.config.options?.elevation) === false ? Number(this.config.options.elevation) : 0;
129
- this.config.options.weather = typeof this.config.options?.weather === 'boolean' ? this.config.options.weather : false;
130
- this.config.options.hksv = typeof this.config.options?.hksv === 'boolean' ? this.config.options.hksv : false;
129
+ this.config.options.weather = this.config.options?.weather === true;
130
+ this.config.options.hksv = this.config.options?.hksv === true;
131
131
 
132
132
  // Get configuration for max number of concurrent 'live view' streams. For HomeKit Secure Video, this will always be 1
133
133
  this.config.options.maxStreams =
@@ -140,6 +140,7 @@ export default class NestAccfactory {
140
140
  // Check if a ffmpeg binary exists in current path OR the specific path via configuration
141
141
  // If using HomeBridge, the default path will be where the Homebridge user folder is, otherwise the current directory
142
142
  this.config.options.ffmpeg = {};
143
+ this.config.options.ffmpeg.debug = this.config.options?.ffmpegDebug === true;
143
144
  this.config.options.ffmpeg.binary = path.resolve(
144
145
  typeof this.config.options?.ffmpegPath === 'string' && this.config.options.ffmpegPath !== ''
145
146
  ? this.config.options.ffmpegPath
@@ -367,7 +368,7 @@ export default class NestAccfactory {
367
368
  this.#connections[connectionUUID].token = googleToken;
368
369
  this.#connections[connectionUUID].cameraAPI = {
369
370
  key: 'Authorization',
370
- value: 'Basic ',
371
+ value: 'Basic ', // NOTE: extra space required
371
372
  token: googleToken,
372
373
  oauth2: googleOAuth2Token,
373
374
  };
@@ -392,7 +393,7 @@ export default class NestAccfactory {
392
393
  this.#connections[connectionUUID].authorised = false;
393
394
  this?.log?.debug &&
394
395
  this.log.debug(
395
- 'Failed to connect using credential details for connection uuid "%s". A periodic retry event will be triggered',
396
+ 'Failed to connect to gateway using credential details for connection uuid "%s". A periodic retry event will be triggered',
396
397
  connectionUUID,
397
398
  );
398
399
  this?.log?.error && this.log.error('Authorisation failed using Google account');
@@ -897,11 +898,15 @@ export default class NestAccfactory {
897
898
  // Tidy up tracked devices since this one is removed
898
899
  if (this.#trackedDevices[this.#rawData?.[resource.resourceId]?.value?.device_identity?.serialNumber] !== undefined) {
899
900
  // Remove any active running timers we have for this device
900
- Object.values(
901
- this.#trackedDevices[this.#rawData[resource.resourceId].value.device_identity.serialNumber]?.timers,
902
- ).forEach((timers) => {
903
- clearInterval(timers);
904
- });
901
+ if (
902
+ this.#trackedDevices[this.#rawData[resource.resourceId].value.device_identity.serialNumber]?.timers !== undefined
903
+ ) {
904
+ Object.values(
905
+ this.#trackedDevices[this.#rawData[resource.resourceId].value.device_identity.serialNumber]?.timers,
906
+ ).forEach((timers) => {
907
+ clearInterval(timers);
908
+ });
909
+ }
905
910
 
906
911
  // Send removed notice onto HomeKit device for it to process
907
912
  this.#eventEmitter.emit(
@@ -911,7 +916,7 @@ export default class NestAccfactory {
911
916
  );
912
917
 
913
918
  // Finally, remove from tracked devices
914
- delete this.#trackedDevices[this.#rawData?.[resource.resourceId].value.device_identity.serialNumber];
919
+ delete this.#trackedDevices[this.#rawData[resource.resourceId].value.device_identity.serialNumber];
915
920
  }
916
921
  delete this.#rawData[resource.resourceId];
917
922
  }
@@ -1299,7 +1304,7 @@ export default class NestAccfactory {
1299
1304
  }
1300
1305
  }
1301
1306
 
1302
- // Finally, after device data for anything we havent tracked yet, if device is not excluded, send updated data to device for it to process
1307
+ // Finally, if device is not excluded, send updated data to device for it to process
1303
1308
  if (deviceData.excluded === false && this.#trackedDevices?.[deviceData?.serialNumber] !== undefined) {
1304
1309
  if (
1305
1310
  this.#rawData[deviceData?.nest_google_uuid]?.source !== undefined &&
@@ -1397,6 +1402,16 @@ export default class NestAccfactory {
1397
1402
  data.hkPairingCode = this.config.devices[data.serialNumber].hkPairingCode;
1398
1403
  }
1399
1404
 
1405
+ // If we have a hkPairingCode defined, we need to generate a hkUsername also
1406
+ if (data.hkPairingCode !== undefined) {
1407
+ // Use a Nest Labs prefix for first 6 digits, followed by a CRC24 based off serial number for last 6 digits.
1408
+ data.hkUsername = ('18B430' + crc24(data.serialNumber.toUpperCase()))
1409
+ .toString('hex')
1410
+ .split(/(..)/)
1411
+ .filter((s) => s)
1412
+ .join(':');
1413
+ }
1414
+
1400
1415
  processed = data;
1401
1416
  // eslint-disable-next-line no-unused-vars
1402
1417
  } catch (error) {
@@ -1732,23 +1747,45 @@ export default class NestAccfactory {
1732
1747
  );
1733
1748
 
1734
1749
  // Work out current mode. ie: off, cool, heat, range and get temperature low (heat) and high (cool)
1735
- RESTTypeData.hvac_mode = this.#rawData?.['shared.' + value.value.serial_number].value.target_temperature_type;
1736
- RESTTypeData.target_temperature_low = this.#rawData?.['shared.' + value.value.serial_number].value.target_temperature_low;
1737
- RESTTypeData.target_temperature_high = this.#rawData?.['shared.' + value.value.serial_number].value.target_temperature_high;
1750
+ RESTTypeData.hvac_mode =
1751
+ this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type !== undefined
1752
+ ? this.#rawData?.['shared.' + value.value.serial_number].value.target_temperature_type
1753
+ : 'off';
1754
+ RESTTypeData.target_temperature =
1755
+ isNaN(this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature) === false
1756
+ ? Number(this.#rawData['shared.' + value.value.serial_number].value.target_temperature)
1757
+ : 0.0;
1758
+ RESTTypeData.target_temperature_low =
1759
+ isNaN(this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_low) === false
1760
+ ? Number(this.#rawData['shared.' + value.value.serial_number].value.target_temperature_low)
1761
+ : 0.0;
1762
+ RESTTypeData.target_temperature_high =
1763
+ isNaN(this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_high) === false
1764
+ ? Number(this.#rawData['shared.' + value.value.serial_number].value.target_temperature_high)
1765
+ : 0.0;
1738
1766
  if (this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type.toUpperCase() === 'COOL') {
1739
1767
  // Target temperature is the cooling point
1740
- RESTTypeData.target_temperature = this.#rawData['shared.' + value.value.serial_number].value.target_temperature_high;
1768
+ RESTTypeData.target_temperature =
1769
+ isNaN(this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_high) === false
1770
+ ? Number(this.#rawData['shared.' + value.value.serial_number].value.target_temperature_high)
1771
+ : 0.0;
1741
1772
  }
1742
1773
  if (this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type.toUpperCase() === 'HEAT') {
1743
1774
  // Target temperature is the heating point
1744
- RESTTypeData.target_temperature = this.#rawData['shared.' + value.value.serial_number].value.target_temperature_low;
1775
+ RESTTypeData.target_temperature =
1776
+ isNaN(this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_low) === false
1777
+ ? Number(this.#rawData['shared.' + value.value.serial_number].value.target_temperature_low)
1778
+ : 0.0;
1745
1779
  }
1746
1780
  if (this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_type.toUpperCase() === 'RANGE') {
1747
1781
  // Target temperature is in between the heating and cooling point
1748
1782
  RESTTypeData.target_temperature =
1749
- (this.#rawData['shared.' + value.value.serial_number].value.target_temperature_low +
1750
- this.#rawData['shared.' + value.value.serial_number].value.target_temperature_high) *
1751
- 0.5;
1783
+ isNaN(this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_low) === false &&
1784
+ isNaN(this.#rawData?.['shared.' + value.value.serial_number]?.value?.target_temperature_high) === false
1785
+ ? (Number(this.#rawData['shared.' + value.value.serial_number].value.target_temperature_low) +
1786
+ Number(this.#rawData['shared.' + value.value.serial_number].value.target_temperature_high)) *
1787
+ 0.5
1788
+ : 0.0;
1752
1789
  }
1753
1790
 
1754
1791
  // Work out if eco mode is active and adjust temperature low/high and target
@@ -2104,7 +2141,8 @@ export default class NestAccfactory {
2104
2141
  try {
2105
2142
  if (
2106
2143
  value?.source === NestAccfactory.DataSource.PROTOBUF &&
2107
- value.value?.streaming_protocol?.supportedProtocols !== undefined &&
2144
+ Array.isArray(value.value?.streaming_protocol?.supportedProtocols) === true &&
2145
+ value.value.streaming_protocol.supportedProtocols.includes('PROTOCOL_WEBRTC') === true &&
2108
2146
  (value.value?.configuration_done?.deviceReady === true ||
2109
2147
  value.value?.camera_migration_status?.state?.where === 'MIGRATED_TO_GOOGLE_HOME')
2110
2148
  ) {
@@ -2289,7 +2327,12 @@ export default class NestAccfactory {
2289
2327
  : 120;
2290
2328
  tempDevice.chimeSwitch = this.config?.devices?.[tempDevice.serialNumber]?.chimeSwitch === true; // Control 'indoor' chime by switch
2291
2329
  tempDevice.localAccess = this.config?.devices?.[tempDevice.serialNumber]?.localAccess === true; // Local network video streaming rather than from cloud from camera/doorbells
2292
- tempDevice.ffmpeg = this.config.options.ffmpeg; // ffmpeg details, path, libraries. No ffmpeg = undefined
2330
+ // eslint-disable-next-line no-undef
2331
+ tempDevice.ffmpeg = structuredClone(this.config.options.ffmpeg); // ffmpeg details, path, libraries. No ffmpeg = undefined
2332
+ if (this.config?.devices?.[tempDevice.serialNumber]?.ffmpegDebug !== undefined) {
2333
+ // Device specific ffmpeg debugging
2334
+ tempDevice.ffmpeg.debug = this.config?.devices?.[tempDevice.serialNumber]?.ffmpegDebug === true;
2335
+ }
2293
2336
  tempDevice.maxStreams =
2294
2337
  isNaN(this.config.options?.maxStreams) === false
2295
2338
  ? Number(this.config.options.maxStreams)
@@ -2414,7 +2457,7 @@ export default class NestAccfactory {
2414
2457
  }
2415
2458
 
2416
2459
  let nest_google_uuid = values.uuid; // Nest/Google structure uuid for this get request
2417
- let connectionUuid = this.#rawData[values.uuid].connection; // Associated connection uuid for the uuid
2460
+ let connectionUuid = this.#rawData[values.uuid].connection; // Associated connection uuid for the device
2418
2461
 
2419
2462
  if (this.#protobufRoot !== null && this.#rawData?.[nest_google_uuid]?.source === NestAccfactory.DataSource.PROTOBUF) {
2420
2463
  let updatedTraits = [];
@@ -2756,7 +2799,7 @@ export default class NestAccfactory {
2756
2799
  subscribeJSONData.objects.push({ object_key: nest_google_uuid, op: 'MERGE', value: { [key]: value } });
2757
2800
  }
2758
2801
 
2759
- // Some elements when setting thermostat data are located in a different object locations than with the device object
2802
+ // Some elements when setting thermostat data are located in a different object location than with the device object
2760
2803
  // Handle this scenario below
2761
2804
  if (nest_google_uuid.startsWith('device.') === true) {
2762
2805
  let RESTStructureUUID = nest_google_uuid;
@@ -2773,6 +2816,7 @@ export default class NestAccfactory {
2773
2816
  (key === 'target_temperature_high' && isNaN(value) === false)
2774
2817
  ) {
2775
2818
  RESTStructureUUID = 'shared.' + nest_google_uuid.split('.')[1];
2819
+ subscribeJSONData.objects.push({ object_key: RESTStructureUUID, op: 'MERGE', value: { target_change_pending: true } });
2776
2820
  }
2777
2821
 
2778
2822
  if (key === 'fan_state' && typeof value === 'boolean') {
@@ -3039,9 +3083,9 @@ export default class NestAccfactory {
3039
3083
  },
3040
3084
  encodedData,
3041
3085
  )
3042
- .then((response) => response.bytes())
3086
+ .then((response) => response.arrayBuffer())
3043
3087
  .then((data) => {
3044
- commandResponse = TraitMapResponse.decode(data).toJSON();
3088
+ commandResponse = TraitMapResponse.decode(Buffer.from(data)).toJSON();
3045
3089
  })
3046
3090
  .catch((error) => {
3047
3091
  this?.log?.debug && this.log.debug('Protobuf gateway service command failed with error. Error was "%s"', error?.code);
@@ -1,7 +1,7 @@
1
1
  // Nest Thermostat
2
2
  // Part of homebridge-nest-accfactory
3
3
  //
4
- // Code version 3/10/2024
4
+ // Code version 15/10/2024
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -185,9 +185,6 @@ export default class NestThermostat extends HomeKitDevice {
185
185
  if (this.occupancyService === undefined) {
186
186
  this.occupancyService = this.accessory.addService(this.hap.Service.OccupancySensor, '', 1);
187
187
  }
188
- if (this.occupancyService.testCharacteristic(this.hap.Characteristic.StatusFault) === false) {
189
- this.occupancyService.addCharacteristic(this.hap.Characteristic.StatusFault);
190
- }
191
188
  this.thermostatService.addLinkedService(this.occupancyService);
192
189
 
193
190
  // Setup battery service if not already present on the accessory
@@ -599,12 +596,13 @@ export default class NestThermostat extends HomeKitDevice {
599
596
 
600
597
  this.thermostatService.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, deviceData.current_temperature);
601
598
 
599
+ // If thermostat isn't online or removed from base, report in HomeKit
602
600
  this.thermostatService.updateCharacteristic(
603
601
  this.hap.Characteristic.StatusFault,
604
602
  deviceData.online === true && deviceData.removed_from_base === false
605
603
  ? this.hap.Characteristic.StatusFault.NO_FAULT
606
604
  : this.hap.Characteristic.StatusFault.GENERAL_FAULT,
607
- ); // If Nest isn't online or removed from base, report in HomeKit
605
+ );
608
606
 
609
607
  this.thermostatService.updateCharacteristic(
610
608
  this.hap.Characteristic.LockPhysicalControls,
@@ -624,6 +622,8 @@ export default class NestThermostat extends HomeKitDevice {
624
622
  }
625
623
 
626
624
  // Using a temperature sensor as active temperature?
625
+ // Probably not the best way for HomeKit, but works ;-)
626
+ // Maybe a custom characteristic would be better?
627
627
  this.thermostatService.updateCharacteristic(this.hap.Characteristic.StatusActive, deviceData.active_rcs_sensor === '');
628
628
 
629
629
  // Update battery status
@@ -648,22 +648,10 @@ export default class NestThermostat extends HomeKitDevice {
648
648
  ? this.hap.Characteristic.OccupancyDetected.OCCUPANCY_DETECTED
649
649
  : this.hap.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED,
650
650
  );
651
- this.occupancyService.updateCharacteristic(
652
- this.hap.Characteristic.StatusFault,
653
- deviceData.online === true && deviceData.removed_from_base === false
654
- ? this.hap.Characteristic.StatusFault.NO_FAULT
655
- : this.hap.Characteristic.StatusFault.GENERAL_FAULT,
656
- ); // If Nest isn't online or removed from base, report in HomeKit
657
651
 
658
652
  // Update seperate humidity sensor if configured todo so
659
653
  if (this.humidityService !== undefined) {
660
654
  this.humidityService.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, deviceData.current_humidity);
661
- this.humidityService.updateCharacteristic(
662
- this.hap.Characteristic.StatusFault,
663
- deviceData.online === true && deviceData.removed_from_base === false
664
- ? this.hap.Characteristic.StatusFault.NO_FAULT
665
- : this.hap.Characteristic.StatusFault.GENERAL_FAULT,
666
- ); // If Nest isn't online or removed from base, report in HomeKit
667
655
  }
668
656
 
669
657
  // Update humity on thermostat
@@ -957,7 +945,7 @@ export default class NestThermostat extends HomeKitDevice {
957
945
  if (this.thermostatService !== undefined && typeof this.historyService?.addHistory === 'function') {
958
946
  let tempEntry = this.historyService.lastHistory(this.thermostatService);
959
947
  if (
960
- tempEntry === null ||
948
+ tempEntry === undefined ||
961
949
  (typeof tempEntry === 'object' && tempEntry.status !== historyEntry.status) ||
962
950
  tempEntry.temperature !== deviceData.current_temperature ||
963
951
  JSON.stringify(tempEntry.target) !== JSON.stringify(historyEntry.target) ||
package/dist/webrtc.js CHANGED
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Handles connection and data from Google WebRTC systems
5
5
  //
6
- // Code version 27/9/2024
6
+ // Code version 15/11/2024
7
7
  // Mark Hulskamp
8
8
  'use strict';
9
9
 
@@ -15,7 +15,7 @@ import werift from 'werift';
15
15
  import EventEmitter from 'node:events';
16
16
  import http2 from 'node:http2';
17
17
  import { Buffer } from 'node:buffer';
18
- import { setInterval, clearInterval } from 'node:timers';
18
+ import { setInterval, clearInterval, setTimeout, clearTimeout } from 'node:timers';
19
19
  import fs from 'node:fs';
20
20
  import path from 'node:path';
21
21
  import crypto from 'node:crypto';
@@ -48,6 +48,7 @@ export default class WebRTC extends Streamer {
48
48
  token = undefined; // oauth2 token
49
49
  localAccess = false; // Do we try direct local access to the camera or via Google Home first
50
50
  extendTimer = undefined; // Stream extend timer
51
+ stalledTimer = undefined; // Timer object for no received data
51
52
  pingTimer = undefined; // Google Hopme Foyer periodic ping
52
53
  blankAudio = AACMONO48000BLANK;
53
54
  video = {}; // Video stream details once connected
@@ -92,7 +93,9 @@ export default class WebRTC extends Streamer {
92
93
  // Class functions
93
94
  async connect() {
94
95
  clearInterval(this.extendTimer);
96
+ clearTimeout(this.stalledTimer);
95
97
  this.extendTimer = undefined;
98
+ this.stalledTimer = undefined;
96
99
  this.#id = undefined;
97
100
 
98
101
  if (this.#googleHomeDeviceUUID === undefined) {
@@ -166,7 +169,7 @@ export default class WebRTC extends Streamer {
166
169
  { type: 'nack', parameter: 'pli' },
167
170
  { type: 'goog-remb' },
168
171
  ],
169
- parameters: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4de020',
172
+ parameters: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
170
173
  payloadType: RTP_VIDEO_PAYLOAD_TYPE,
171
174
  }),
172
175
  ],
@@ -275,9 +278,11 @@ export default class WebRTC extends Streamer {
275
278
  });
276
279
 
277
280
  if (homeFoyerResponse?.data?.[0]?.streamExtensionStatus !== 'STATUS_STREAM_EXTENDED') {
278
- this?.log?.debug && this.log.debug('Error occured while requested stream extentions for uuid "%s"', this.uuid);
281
+ this?.log?.debug && this.log.debug('Error occurred while requested stream extension for uuid "%s"', this.uuid);
279
282
 
280
- // Do we try to reconnect???
283
+ if (typeof this.#peerConnection?.close === 'function') {
284
+ await this.#peerConnection.close();
285
+ }
281
286
  }
282
287
  }
283
288
  }, EXTENDINTERVAL);
@@ -317,7 +322,9 @@ export default class WebRTC extends Streamer {
317
322
  }
318
323
 
319
324
  clearInterval(this.extendTimer);
325
+ clearInterval(this.stalledTimer);
320
326
  this.extendTimer = undefined;
327
+ this.stalledTimer = undefined;
321
328
  this.#id = undefined;
322
329
  this.#googleHomeFoyer = undefined;
323
330
  this.#peerConnection = undefined;
@@ -365,7 +372,7 @@ export default class WebRTC extends Streamer {
365
372
 
366
373
  if (homeFoyerResponse?.status !== 0) {
367
374
  this.audio.talking = undefined;
368
- this?.log?.debug && this.log.debug('Error occured while requesting talkback to start for uuid "%s"', this.uuid);
375
+ this?.log?.debug && this.log.debug('Error occurred while requesting talkback to start for uuid "%s"', this.uuid);
369
376
  }
370
377
  if (homeFoyerResponse?.status === 0) {
371
378
  this.audio.talking = true;
@@ -380,7 +387,7 @@ export default class WebRTC extends Streamer {
380
387
  rtpHeader.marker = true;
381
388
  rtpHeader.payloadOffset = RTP_PACKET_HEADER_SIZE;
382
389
  rtpHeader.payloadType = this.audio.id; // As the camera is send/recv, we use the same payload type id as the incoming audio
383
- rtpHeader.timestamp = Date.now() & 0xffffffff; // Think the time stanp difference should be 960 per audio packet?
390
+ rtpHeader.timestamp = Date.now() & 0xffffffff; // Think the time stamp difference should be 960ms per audio packet?
384
391
  rtpHeader.sequenceNumber = this.audio.talkSquenceNumber++ & 0xffff;
385
392
  let rtpPacket = new werift.RtpPacket(rtpHeader, talkingData);
386
393
  this.#audioTransceiver.sender.sendRtp(rtpPacket.serialize());
@@ -397,7 +404,7 @@ export default class WebRTC extends Streamer {
397
404
  command: 'COMMAND_STOP',
398
405
  });
399
406
  if (homeFoyerResponse?.status !== 0) {
400
- this?.log?.debug && this.log.debug('Error occured while requesting talkback to stop for uuid "%s"', this.uuid);
407
+ this?.log?.debug && this.log.debug('Error occurred while requesting talkback to stop for uuid "%s"', this.uuid);
401
408
  }
402
409
  if (homeFoyerResponse?.status === 0) {
403
410
  this?.log?.debug && this.log.debug('Talking ended on uuid "%s"', this.uuid);
@@ -439,6 +446,22 @@ export default class WebRTC extends Streamer {
439
446
  return;
440
447
  }
441
448
 
449
+ // Setup up a timeout to monitor for no packets recieved in a certain period
450
+ // If its trigger, we'll attempt to restart the stream and/or connection
451
+ clearTimeout(this.stalledTimer);
452
+ this.stalledTimer = setTimeout(async () => {
453
+ this?.log?.debug &&
454
+ this.log.debug(
455
+ 'We have not received any data from webrtc in the past "%s" seconds for uuid "%s". Attempting restart',
456
+ 10,
457
+ this.uuid,
458
+ );
459
+
460
+ if (typeof this.#peerConnection?.close === 'function') {
461
+ await this.#peerConnection.close();
462
+ }
463
+ }, 10000);
464
+
442
465
  if (weriftRtpPacket.header.payloadType !== undefined && weriftRtpPacket.header.payloadType === this.video?.id) {
443
466
  // Process video RTP packets. Need to re-assemble the H264 NALUs into a single H264 frame we can output
444
467
  if (weriftRtpPacket.header.padding === false) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "homebridge-nest-accfactory",
3
- "displayName": "Homebridge Nest Accfactory",
3
+ "displayName": "Nest Accfactory",
4
4
  "type": "module",
5
- "version": "0.2.2",
5
+ "version": "0.2.3",
6
6
  "description": "Homebridge support for Nest/Google devices including HomeKit Secure Video (HKSV) support for doorbells and cameras",
7
7
  "author": "n0rt0nthec4t",
8
8
  "license": "Apache-2.0",
@@ -14,6 +14,7 @@
14
14
  "bugs": {
15
15
  "url": "https://github.com/n0rt0nthec4t/homebridge-nest-accfactory/issues"
16
16
  },
17
+ "funding": "https://github.com/n0rt0nthec4t/homebridge-nest-accfactory?sponsor=1",
17
18
  "keywords": [
18
19
  "homekit",
19
20
  "homebridge-plugin",
@@ -46,24 +47,24 @@
46
47
  "clean": "rimraf ./dist*",
47
48
  "format": "prettier --write src/*.js src/**/*.js",
48
49
  "lint": "eslint src/*.js src/**/*.js --max-warnings=20",
49
- "build": "npm run clean && copyfiles -u 1 src/*.js dist && copyfiles -u 1 src/res/*.h264 dist && copyfiles -u 1 src/res/*.jpg dist && copyfiles -u 1 'src/protobuf/**/*.proto' dist",
50
+ "build": "npm run clean && copyfiles -u 1 src/*.js dist && copyfiles -u 2 src/HomeKitDevice/*.js dist && copyfiles -u 2 src/HomeKitHistory/*.js dist && copyfiles -u 1 src/res/*.h264 dist && copyfiles -u 1 src/res/*.jpg dist && copyfiles -u 1 'src/protobuf/**/*.proto' dist",
50
51
  "prepublishOnly": "npm run lint && npm run build"
51
52
  },
52
53
  "devDependencies": {
53
- "@eslint/js": "^9.11.1",
54
- "@stylistic/eslint-plugin": "^2.8.0",
55
- "@types/node": "^22.7.4",
56
- "@typescript-eslint/parser": "^8.8.0",
54
+ "@eslint/js": "^9.16.0",
55
+ "@stylistic/eslint-plugin": "^2.11.0",
56
+ "@types/node": "^22.10.1",
57
+ "@typescript-eslint/parser": "^8.17.0",
57
58
  "homebridge": "^2.0.0-beta.0",
58
59
  "copyfiles": "^2.4.1",
59
- "eslint": "^9.11.1",
60
- "prettier": "^3.3.3",
60
+ "eslint": "^9.16.0",
61
+ "prettier": "^3.4.2",
61
62
  "prettier-eslint": "^16.3.0",
62
63
  "rimraf": "^6.0.1"
63
64
  },
64
65
  "dependencies": {
65
66
  "protobufjs": "^7.4.0",
66
67
  "ws": "^8.18.0",
67
- "werift": "^0.20.0"
68
+ "werift": "^0.20.1"
68
69
  }
69
70
  }