homebridge-nest-accfactory 0.3.0 → 0.3.1

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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to `homebridge-nest-accfactory` will be documented in this file. This project tries to adhere to [Semantic Versioning](http://semver.org/).
4
4
 
5
+ ## v0.3.1 (2025/06/16)
6
+
7
+ - Minor stability improvements affecting standalone docker version
8
+
5
9
  ## v0.3.0 (2025/06/14)
6
10
 
7
11
  - General code cleanup and stability improvements
@@ -45,30 +45,31 @@ import crypto from 'crypto';
45
45
  import EventEmitter from 'node:events';
46
46
 
47
47
  // Define constants
48
- const HK_PIN_3_2_3 = /^\d{3}-\d{2}-\d{3}$/;
49
- const HK_PIN_4_4 = /^\d{4}-\d{4}$/;
50
- const MAC_ADDR = /^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/;
51
- const LOGLEVELS = {
52
- info: 'info',
53
- success: 'success',
54
- warn: 'warn',
55
- error: 'error',
56
- debug: 'debug',
48
+ const LOG_LEVELS = {
49
+ INFO: 'info',
50
+ SUCCESS: 'success',
51
+ WARN: 'warn',
52
+ ERROR: 'error',
53
+ DEBUG: 'debug',
57
54
  };
58
55
 
59
56
  // Define our HomeKit device class
60
- class HomeKitDevice {
57
+ export default class HomeKitDevice {
61
58
  static UPDATE = 'HomeKitDevice.update'; // Device update message
62
59
  static REMOVE = 'HomeKitDevice.remove'; // Device remove message
63
60
  static SET = 'HomeKitDevice.set'; // Device set property message
64
61
  static GET = 'HomeKitDevice.get'; // Device get property message
65
62
 
63
+ static HK_PIN_3_2_3 = /^\d{3}-\d{2}-\d{3}$/;
64
+ static HK_PIN_4_4 = /^\d{4}-\d{4}$/;
65
+ static MAC_ADDR = /^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/;
66
+
66
67
  // Override this in the class which extends
67
68
  static PLUGIN_NAME = undefined; // Homebridge plugin name
68
69
  static PLATFORM_NAME = undefined; // Homebridge platform name
69
70
  static HISTORY = undefined; // HomeKit History object
70
71
  static TYPE = 'base'; // String naming type of device
71
- static VERSION = '2025.06.12'; // Code version
72
+ static VERSION = '2025.06.15'; // Code version
72
73
 
73
74
  deviceData = {}; // The devices data we store
74
75
  historyService = undefined; // HomeKit history service
@@ -82,9 +83,9 @@ class HomeKitDevice {
82
83
  #eventEmitter = undefined; // Event emitter to use for comms
83
84
  #postSetupDetails = []; // Use for extra output details once a device has been setup
84
85
 
85
- constructor(accessory, api, log, eventEmitter, deviceData) {
86
+ constructor(accessory = undefined, api = undefined, log = undefined, eventEmitter = undefined, deviceData = {}) {
86
87
  // Validate the passed in logging object. We are expecting certain functions to be present
87
- if (Object.keys(LOGLEVELS).every((fn) => typeof log?.[fn] === 'function')) {
88
+ if (Object.values(LOG_LEVELS).every((fn) => typeof log?.[fn] === 'function')) {
88
89
  this.log = log;
89
90
  }
90
91
 
@@ -94,14 +95,14 @@ class HomeKitDevice {
94
95
  this.hap = api.hap;
95
96
  this.#platform = api;
96
97
 
97
- this.postSetupDetail('Homebridge backend', LOGLEVELS.debug);
98
+ this.postSetupDetail('Homebridge backend', LOG_LEVELS.DEBUG);
98
99
  }
99
100
 
100
101
  if (typeof api?.HAPLibraryVersion === 'function' && api?.version === undefined && api?.hap === undefined) {
101
102
  // As we're missing the Homebridge entry points but have the HAP library version
102
103
  this.hap = api;
103
104
 
104
- this.postSetupDetail('HAP-NodeJS library', LOGLEVELS.debug);
105
+ this.postSetupDetail('HAP-NodeJS library', LOG_LEVELS.DEBUG);
105
106
  }
106
107
 
107
108
  // Generate UUID for this device instance
@@ -157,10 +158,10 @@ class HomeKitDevice {
157
158
  this.deviceData.manufacturer === '' ||
158
159
  (this.#platform === undefined &&
159
160
  (typeof this.deviceData?.hkPairingCode !== 'string' ||
160
- (new RegExp(HK_PIN_3_2_3).test(this.deviceData.hkPairingCode) === false &&
161
- new RegExp(HK_PIN_4_4).test(this.deviceData.hkPairingCode) === false) ||
161
+ (HomeKitDevice.HK_PIN_3_2_3.test(this.deviceData.hkPairingCode) === false &&
162
+ HomeKitDevice.HK_PIN_4_4.test(this.deviceData.hkPairingCode) === false) ||
162
163
  typeof this.deviceData?.hkUsername !== 'string' ||
163
- new RegExp(MAC_ADDR).test(this.deviceData.hkUsername) === false))
164
+ HomeKitDevice.MAC_ADDR.test(this.deviceData.hkUsername) === false))
164
165
  ) {
165
166
  return;
166
167
  }
@@ -198,7 +199,7 @@ class HomeKitDevice {
198
199
 
199
200
  if (typeof this?.setupDevice === 'function') {
200
201
  try {
201
- this.postSetupDetail('Serial number "%s"', this.deviceData.serialNumber, LOGLEVELS.debug);
202
+ this.postSetupDetail('Serial number "%s"', this.deviceData.serialNumber, LOG_LEVELS.DEBUG);
202
203
 
203
204
  await this.setupDevice();
204
205
 
@@ -207,13 +208,16 @@ class HomeKitDevice {
207
208
  }
208
209
 
209
210
  this?.log?.info?.('Setup %s %s as "%s"', this.deviceData.manufacturer, this.deviceData.model, this.deviceData.description);
210
-
211
211
  this.#postSetupDetails.forEach((entry) => {
212
212
  if (typeof entry === 'string') {
213
- this?.log?.[LOGLEVELS.info]?.(' += %s', entry);
213
+ this?.log?.[LOG_LEVELS.INFO]?.(' += %s', entry);
214
214
  } else if (typeof entry?.message === 'string') {
215
215
  let level =
216
- Object.hasOwn(LOGLEVELS, entry?.level) && typeof this?.log?.[entry?.level] === 'function' ? entry.level : LOGLEVELS.info;
216
+ Object.hasOwn(LOG_LEVELS, entry?.level?.toUpperCase?.()) &&
217
+ typeof this?.log?.[LOG_LEVELS[entry.level.toUpperCase()]] === 'function'
218
+ ? LOG_LEVELS[entry.level.toUpperCase()]
219
+ : LOG_LEVELS.INFO;
220
+
217
221
  this?.log?.[level]?.(' += ' + entry.message, ...(Array.isArray(entry?.args) ? entry.args : []));
218
222
  }
219
223
  });
@@ -236,7 +240,7 @@ class HomeKitDevice {
236
240
  this?.log?.info(' += Advertising as "%s"', this.accessory.displayName);
237
241
  this?.log?.info(' += Pairing code is "%s"', this.accessory.pincode);
238
242
  }
239
- this.#postSetupDetails = []; // Dont' need these anymore
243
+ this.#postSetupDetails = []; // Don't need these anymore
240
244
  return this.accessory; // Return our HomeKit accessory
241
245
  }
242
246
 
@@ -499,19 +503,16 @@ class HomeKitDevice {
499
503
  return;
500
504
  }
501
505
 
502
- let level = 'info';
503
- let availableLevel = Object.keys(LOGLEVELS).find((lvl) => typeof this.log?.[lvl] === 'function') || 'info';
506
+ let levelKey = 'INFO';
504
507
  let lastArg = args.at(-1);
505
508
 
506
- if (typeof lastArg === 'string' && Object.hasOwn(LOGLEVELS, lastArg)) {
507
- level = lastArg;
509
+ if (typeof lastArg === 'string' && Object.hasOwn(LOG_LEVELS, lastArg.toUpperCase())) {
510
+ levelKey = lastArg.toUpperCase();
508
511
  args = args.slice(0, -1);
509
- } else {
510
- level = availableLevel;
511
512
  }
512
513
 
513
514
  this.#postSetupDetails.push({
514
- level,
515
+ level: LOG_LEVELS[levelKey], // 'info', 'debug', etc.
515
516
  message,
516
517
  args: args.length > 0 ? args : undefined,
517
518
  });
@@ -544,8 +545,16 @@ class HomeKitDevice {
544
545
 
545
546
  return uuid;
546
547
  }
547
- }
548
548
 
549
- // Define exports
550
- export { HK_PIN_3_2_3, HK_PIN_4_4, MAC_ADDR, HomeKitDevice };
551
- export default HomeKitDevice;
549
+ static makeHomeKitName(name) {
550
+ // Strip invalid characters to meet HomeKit naming requirements
551
+ // Ensure only letters or numbers are at the beginning AND/OR end of string
552
+ // Matches against uni-code characters
553
+ return typeof name === 'string'
554
+ ? name
555
+ .replace(/[^\p{L}\p{N}\p{Z}\u2019.,-]/gu, '')
556
+ .replace(/^[^\p{L}\p{N}]*/gu, '')
557
+ .replace(/[^\p{L}\p{N}]+$/gu, '')
558
+ : name;
559
+ }
560
+ }
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Configuration validation and processing
2
2
  // Part of homebridge-nest-accfactory
3
3
  //
4
- // Code version 2025.06.12
4
+ // Code version 2025.06.15
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -13,10 +13,10 @@ import process from 'node:process';
13
13
  import child_process from 'node:child_process';
14
14
 
15
15
  // Define constants
16
- const FFMPEGVERSION = '6.0.0';
17
- const AccountType = {
18
- Nest: 'Nest',
19
- Google: 'Google',
16
+ const FFMPEG_VERSION = '6.0.0';
17
+ const ACCOUNT_TYPE = {
18
+ NEST: 'Nest',
19
+ GOOGLE: 'Google',
20
20
  };
21
21
 
22
22
  function processConfig(config, log) {
@@ -80,7 +80,7 @@ function processConfig(config, log) {
80
80
  options.ffmpeg.libfdk_aac = stdout.includes('--enable-libfdk-aac') === true;
81
81
 
82
82
  let versionTooOld =
83
- options.ffmpeg.version?.localeCompare(FFMPEGVERSION, undefined, {
83
+ options.ffmpeg.version?.localeCompare(FFMPEG_VERSION, undefined, {
84
84
  numeric: true,
85
85
  sensitivity: 'case',
86
86
  caseFirst: 'upper',
@@ -96,7 +96,7 @@ function processConfig(config, log) {
96
96
  log?.warn?.('ffmpeg binary "%s" does not meet the minimum support requirements', options.ffmpeg.binary);
97
97
 
98
98
  if (versionTooOld) {
99
- log?.warn?.('Minimum binary version is "%s", however the installed version is "%s"', FFMPEGVERSION, options.ffmpeg.version);
99
+ log?.warn?.('Minimum binary version is "%s", however the installed version is "%s"', FFMPEG_VERSION, options.ffmpeg.version);
100
100
  log?.warn?.('Stream video/recording from camera/doorbells will be unavailable');
101
101
  options.ffmpeg.binary = undefined; // No ffmpeg since below min version
102
102
  }
@@ -167,7 +167,7 @@ function buildConnections(config) {
167
167
  let fieldTest = section?.fieldTest === true;
168
168
  connections[crypto.randomUUID()] = {
169
169
  name: key,
170
- type: AccountType.Nest,
170
+ type: ACCOUNT_TYPE.NEST,
171
171
  authorised: false,
172
172
  access_token: section.access_token,
173
173
  fieldTest,
@@ -187,7 +187,7 @@ function buildConnections(config) {
187
187
  let fieldTest = section?.fieldTest === true;
188
188
  connections[crypto.randomUUID()] = {
189
189
  name: key,
190
- type: AccountType.Google,
190
+ type: ACCOUNT_TYPE.GOOGLE,
191
191
  authorised: false,
192
192
  issuetoken: section.issuetoken,
193
193
  cookie: section.cookie,
@@ -204,4 +204,4 @@ function buildConnections(config) {
204
204
  }
205
205
 
206
206
  // Define exports
207
- export { AccountType, processConfig, buildConnections };
207
+ export { ACCOUNT_TYPE, processConfig, buildConnections };
package/dist/devices.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Device support loader
2
2
  // Part of homebridge-nest-accfactory
3
3
  //
4
- // Code version 2025.06.12
4
+ // Code version 2025.06.15
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -15,7 +15,7 @@ import HomeKitDevice from './HomeKitDevice.js';
15
15
 
16
16
  // Define constants
17
17
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
18
- const DeviceType = Object.freeze({
18
+ const DEVICE_TYPE = Object.freeze({
19
19
  THERMOSTAT: 'Thermostat',
20
20
  TEMPSENSOR: 'TemperatureSensor',
21
21
  SMOKESENSOR: 'Protect',
@@ -82,27 +82,32 @@ async function loadDeviceModules(log, pluginDir = '') {
82
82
  function getDeviceHKCategory(type) {
83
83
  let category = 1; // Categories.OTHER
84
84
 
85
- if (type === DeviceType.LOCK) {
85
+ if (type === DEVICE_TYPE.LOCK) {
86
86
  category = 6; // Categories.DOOR_LOCK
87
87
  }
88
88
 
89
- if (type === DeviceType.THERMOSTAT) {
89
+ if (type === DEVICE_TYPE.THERMOSTAT) {
90
90
  category = 9; // Categories.THERMOSTAT
91
91
  }
92
92
 
93
- if (type === DeviceType.TEMPSENSOR || type === DeviceType.HEATLINK || type === DeviceType.SMOKESENSOR || type === DeviceType.WEATHER) {
93
+ if (
94
+ type === DEVICE_TYPE.TEMPSENSOR ||
95
+ type === DEVICE_TYPE.HEATLINK ||
96
+ type === DEVICE_TYPE.SMOKESENSOR ||
97
+ type === DEVICE_TYPE.WEATHER
98
+ ) {
94
99
  category = 10; // Categories.SENSOR
95
100
  }
96
101
 
97
- if (type === DeviceType.ALARM) {
102
+ if (type === DEVICE_TYPE.ALARM) {
98
103
  category = 11; // Categories.SECURITY_SYSTEM
99
104
  }
100
105
 
101
- if (type === DeviceType.CAMERA || type === DeviceType.FLOODLIGHT) {
106
+ if (type === DEVICE_TYPE.CAMERA || type === DEVICE_TYPE.FLOODLIGHT) {
102
107
  category = 17; // Categories.IP_CAMERA
103
108
  }
104
109
 
105
- if (type === DeviceType.DOORBELL) {
110
+ if (type === DEVICE_TYPE.DOORBELL) {
106
111
  category = 18; // Categories.VIDEO_DOORBELL
107
112
  }
108
113
 
@@ -110,4 +115,4 @@ function getDeviceHKCategory(type) {
110
115
  }
111
116
 
112
117
  // Define exports
113
- export { DeviceType, loadDeviceModules, getDeviceHKCategory };
118
+ export { DEVICE_TYPE, loadDeviceModules, getDeviceHKCategory };
package/dist/nexustalk.js CHANGED
@@ -5,7 +5,7 @@
5
5
  //
6
6
  // Credit to https://github.com/Brandawg93/homebridge-nest-cam for the work on the Nest Camera comms code on which this is based
7
7
  //
8
- // Code version 2025.06.10
8
+ // Code version 2025.06.15
9
9
  // Mark Hulskamp
10
10
  'use strict';
11
11
 
@@ -25,11 +25,11 @@ import { fileURLToPath } from 'node:url';
25
25
  import Streamer from './streamer.js';
26
26
 
27
27
  // Define constants
28
- const PINGINTERVAL = 15000; // Ping interval to nexus server while stream active
29
- const USERAGENT = 'Nest/5.78.0 (iOScom.nestlabs.jasper.release) os=18.0'; // User Agent string
28
+ const PING_INTERVAL = 15000; // Ping interval to nexus server while stream active
29
+ const USER_AGENT = 'Nest/5.78.0 (iOScom.nestlabs.jasper.release) os=18.0'; // User Agent string
30
30
  const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
31
31
 
32
- const PacketType = {
32
+ const PACKET_TYPE = {
33
33
  PING: 1,
34
34
  HELLO: 100,
35
35
  PING_CAMERA: 101,
@@ -56,7 +56,7 @@ const PacketType = {
56
56
  };
57
57
 
58
58
  // Blank audio in AAC format, mono channel @48000
59
- const AACMONO48000BLANK = Buffer.from([
59
+ const AAC_MONO_48000_BLANK = Buffer.from([
60
60
  0xff, 0xf1, 0x4c, 0x40, 0x03, 0x9f, 0xfc, 0xde, 0x02, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x39, 0x2e, 0x31, 0x38, 0x2e, 0x31, 0x30, 0x30,
61
61
  0x00, 0x02, 0x30, 0x40, 0x0e,
62
62
  ]);
@@ -68,7 +68,7 @@ export default class NexusTalk extends Streamer {
68
68
  pingTimer = undefined; // Timer object for ping interval
69
69
  stalledTimer = undefined; // Timer object for no received data
70
70
  host = ''; // Host to connect to or connected too
71
- blankAudio = AACMONO48000BLANK;
71
+ blankAudio = AAC_MONO_48000_BLANK;
72
72
  video = {}; // Video stream details once connected
73
73
  audio = {}; // Audio stream details once connected
74
74
 
@@ -219,7 +219,7 @@ export default class NexusTalk extends Streamer {
219
219
  sampleRate: 16000,
220
220
  }),
221
221
  ).finish();
222
- this.#sendMessage(PacketType.AUDIO_PAYLOAD, encodedData);
222
+ this.#sendMessage(PACKET_TYPE.AUDIO_PAYLOAD, encodedData);
223
223
  }
224
224
  }
225
225
  }
@@ -248,7 +248,7 @@ export default class NexusTalk extends Streamer {
248
248
  profileNotFoundAction: 'REDIRECT',
249
249
  }),
250
250
  ).finish();
251
- this.#sendMessage(PacketType.START_PLAYBACK, encodedData);
251
+ this.#sendMessage(PACKET_TYPE.START_PLAYBACK, encodedData);
252
252
  }
253
253
  }
254
254
 
@@ -261,13 +261,13 @@ export default class NexusTalk extends Streamer {
261
261
  sessionId: this.#id,
262
262
  }),
263
263
  ).finish();
264
- this.#sendMessage(PacketType.STOP_PLAYBACK, encodedData);
264
+ this.#sendMessage(PACKET_TYPE.STOP_PLAYBACK, encodedData);
265
265
  }
266
266
  }
267
267
  }
268
268
 
269
269
  #sendMessage(type, data) {
270
- if (this.#socket?.readyState !== 'open' || (type !== PacketType.HELLO && this.#authorised === false)) {
270
+ if (this.#socket?.readyState !== 'open' || (type !== PACKET_TYPE.HELLO && this.#authorised === false)) {
271
271
  // We're not connect and/or authorised yet, so 'cache' message for processing once this occurs
272
272
  this.#messages.push({ type: type, data: data });
273
273
  return;
@@ -275,11 +275,11 @@ export default class NexusTalk extends Streamer {
275
275
 
276
276
  // Create nexusTalk message header
277
277
  let header = Buffer.alloc(3);
278
- if (type !== PacketType.LONG_PLAYBACK_PACKET) {
278
+ if (type !== PACKET_TYPE.LONG_PLAYBACK_PACKET) {
279
279
  header.writeUInt8(type, 0);
280
280
  header.writeUInt16BE(data.length, 1);
281
281
  }
282
- if (type === PacketType.LONG_PLAYBACK_PACKET) {
282
+ if (type === PACKET_TYPE.LONG_PLAYBACK_PACKET) {
283
283
  header = Buffer.alloc(5);
284
284
  header.writeUInt8(type, 0);
285
285
  header.writeUInt32BE(data.length, 1);
@@ -309,7 +309,7 @@ export default class NexusTalk extends Streamer {
309
309
  if (reauthorise === true && authoriseRequest !== null) {
310
310
  // Request to re-authorise only
311
311
  this?.log?.debug?.('Re-authentication requested to "%s"', this.host);
312
- this.#sendMessage(PacketType.AUTHORIZE_REQUEST, authoriseRequest);
312
+ this.#sendMessage(PACKET_TYPE.AUTHORIZE_REQUEST, authoriseRequest);
313
313
  }
314
314
 
315
315
  if (reauthorise === false && authoriseRequest !== null) {
@@ -323,13 +323,13 @@ export default class NexusTalk extends Streamer {
323
323
  protocolVersion: 'VERSION_3',
324
324
  uuid: this.uuid.split(/[._]+/)[1],
325
325
  requireConnectedCamera: false,
326
- userAgent: USERAGENT,
326
+ USER_AGENT: USER_AGENT,
327
327
  deviceId: crypto.randomUUID(),
328
328
  clientType: 'IOS',
329
329
  authoriseRequest: authoriseRequest,
330
330
  }),
331
331
  ).finish();
332
- this.#sendMessage(PacketType.HELLO, encodedData);
332
+ this.#sendMessage(PACKET_TYPE.HELLO, encodedData);
333
333
  }
334
334
  }
335
335
  }
@@ -494,7 +494,7 @@ export default class NexusTalk extends Streamer {
494
494
  let packetType = this.#packets.readUInt8(0);
495
495
  let packetSize = this.#packets.readUInt16BE(1);
496
496
 
497
- if (packetType === PacketType.LONG_PLAYBACK_PACKET) {
497
+ if (packetType === PACKET_TYPE.LONG_PLAYBACK_PACKET) {
498
498
  headerSize = 5;
499
499
  packetSize = this.#packets.readUInt32BE(1);
500
500
  }
@@ -509,11 +509,11 @@ export default class NexusTalk extends Streamer {
509
509
  this.#packets = this.#packets.subarray(headerSize + packetSize);
510
510
 
511
511
  switch (packetType) {
512
- case PacketType.PING: {
512
+ case PACKET_TYPE.PING: {
513
513
  break;
514
514
  }
515
515
 
516
- case PacketType.OK: {
516
+ case PACKET_TYPE.OK: {
517
517
  // process any pending messages we have stored
518
518
  this.#authorised = true; // OK message, means we're connected and authorised to Nexus
519
519
  for (let message = this.#messages.shift(); message; message = this.#messages.shift()) {
@@ -523,46 +523,46 @@ export default class NexusTalk extends Streamer {
523
523
  // Periodically send PING message to keep stream alive
524
524
  clearInterval(this.pingTimer);
525
525
  this.pingTimer = setInterval(() => {
526
- this.#sendMessage(PacketType.PING, Buffer.alloc(0));
527
- }, PINGINTERVAL);
526
+ this.#sendMessage(PACKET_TYPE.PING, Buffer.alloc(0));
527
+ }, PING_INTERVAL);
528
528
 
529
529
  // Start processing data
530
530
  this.#startNexusData();
531
531
  break;
532
532
  }
533
533
 
534
- case PacketType.ERROR: {
534
+ case PACKET_TYPE.ERROR: {
535
535
  this.#handleNexusError(protoBufPayload);
536
536
  break;
537
537
  }
538
538
 
539
- case PacketType.PLAYBACK_BEGIN: {
539
+ case PACKET_TYPE.PLAYBACK_BEGIN: {
540
540
  this.#handlePlaybackBegin(protoBufPayload);
541
541
  break;
542
542
  }
543
543
 
544
- case PacketType.PLAYBACK_END: {
544
+ case PACKET_TYPE.PLAYBACK_END: {
545
545
  this.#handlePlaybackEnd(protoBufPayload);
546
546
  break;
547
547
  }
548
548
 
549
- case PacketType.PLAYBACK_PACKET:
550
- case PacketType.LONG_PLAYBACK_PACKET: {
549
+ case PACKET_TYPE.PLAYBACK_PACKET:
550
+ case PACKET_TYPE.LONG_PLAYBACK_PACKET: {
551
551
  this.#handlePlaybackPacket(protoBufPayload);
552
552
  break;
553
553
  }
554
554
 
555
- case PacketType.REDIRECT: {
555
+ case PACKET_TYPE.REDIRECT: {
556
556
  this.#handleRedirect(protoBufPayload);
557
557
  break;
558
558
  }
559
559
 
560
- case PacketType.TALKBACK_BEGIN: {
560
+ case PACKET_TYPE.TALKBACK_BEGIN: {
561
561
  this.#handleTalkbackBegin(protoBufPayload);
562
562
  break;
563
563
  }
564
564
 
565
- case PacketType.TALKBACK_END: {
565
+ case PACKET_TYPE.TALKBACK_END: {
566
566
  this.#handleTalkbackEnd(protoBufPayload);
567
567
  break;
568
568
  }
@@ -21,21 +21,23 @@ import HomeKitDevice from '../HomeKitDevice.js';
21
21
  import NexusTalk from '../nexustalk.js';
22
22
  import WebRTC from '../webrtc.js';
23
23
 
24
- const CAMERARESOURCE = {
25
- offline: 'Nest_camera_offline.jpg',
26
- off: 'Nest_camera_off.jpg',
27
- transfer: 'Nest_camera_transfer.jpg',
24
+ const CAMERA_RESOURCE = {
25
+ OFFLINE: 'Nest_camera_offline.jpg',
26
+ OFF: 'Nest_camera_off.jpg',
27
+ TRANSFER: 'Nest_camera_transfer.jpg',
28
28
  };
29
29
  const MP4BOX = 'mp4box'; // MP4 box fragement event for HKSV recording
30
- const SNAPSHOTCACHETIMEOUT = 30000; // Timeout for retaining snapshot image (in milliseconds)
31
- const PROTOCOLWEBRTC = 'PROTOCOL_WEBRTC';
32
- const PROTOCOLNEXUSTALK = 'PROTOCOL_NEXUSTALK';
33
- const RESOURCEPATH = '../res';
30
+ const SNAPSHOT_CACHE_TIMEOUT = 30000; // Timeout for retaining snapshot image (in milliseconds)
31
+ const STREAMING_PROTOCOL = {
32
+ WEBRTC: 'PROTOCOL_WEBRTC',
33
+ NEXUSTALK: 'PROTOCOL_NEXUSTALK',
34
+ };
35
+ const RESOURCE_PATH = '../res';
34
36
  const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
35
37
 
36
38
  export default class NestCamera extends HomeKitDevice {
37
39
  static TYPE = 'Camera';
38
- static VERSION = '2025.06.12';
40
+ static VERSION = '2025.06.15';
39
41
 
40
42
  controller = undefined; // HomeKit Camera/Doorbell controller service
41
43
  streamer = undefined; // Streamer object for live/recording stream
@@ -61,7 +63,7 @@ export default class NestCamera extends HomeKitDevice {
61
63
  // Load supporrt image files as required
62
64
  const loadImageIfExists = (filename, label) => {
63
65
  let buffer = undefined;
64
- let file = path.resolve(__dirname, RESOURCEPATH, filename);
66
+ let file = path.resolve(__dirname, RESOURCE_PATH, filename);
65
67
  if (fs.existsSync(file) === true) {
66
68
  buffer = fs.readFileSync(file);
67
69
  } else {
@@ -70,9 +72,9 @@ export default class NestCamera extends HomeKitDevice {
70
72
  return buffer;
71
73
  };
72
74
 
73
- this.#cameraOfflineImage = loadImageIfExists(CAMERARESOURCE.offline, 'offline');
74
- this.#cameraVideoOffImage = loadImageIfExists(CAMERARESOURCE.off, 'video off');
75
- this.#cameraTransferringImage = loadImageIfExists(CAMERARESOURCE.transfer, 'transferring');
75
+ this.#cameraOfflineImage = loadImageIfExists(CAMERA_RESOURCE.OFFLINE, 'offline');
76
+ this.#cameraVideoOffImage = loadImageIfExists(CAMERA_RESOURCE.OFF, 'video off');
77
+ this.#cameraTransferringImage = loadImageIfExists(CAMERA_RESOURCE.TRANSFER, 'transferring');
76
78
  }
77
79
 
78
80
  // Class functions
@@ -201,10 +203,10 @@ export default class NestCamera extends HomeKitDevice {
201
203
  }
202
204
 
203
205
  if (
204
- (this.deviceData.streaming_protocols.includes(PROTOCOLWEBRTC) === false &&
205
- this.deviceData.streaming_protocols.includes(PROTOCOLNEXUSTALK) === false) ||
206
- (this.deviceData.streaming_protocols.includes(PROTOCOLWEBRTC) === true && WebRTC === undefined) ||
207
- (this.deviceData.streaming_protocols.includes(PROTOCOLNEXUSTALK) === true && NexusTalk === undefined)
206
+ (this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.WEBRTC) === false &&
207
+ this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.NEXUSTALK) === false) ||
208
+ (this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.WEBRTC) === true && WebRTC === undefined) ||
209
+ (this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.NEXUSTALK) === true && NexusTalk === undefined)
208
210
  ) {
209
211
  this?.log?.error?.(
210
212
  'No suitable streaming protocol is present for "%s". Streaming and recording will be unavailable',
@@ -556,7 +558,7 @@ export default class NestCamera extends HomeKitDevice {
556
558
  clearTimeout(this.snapshotTimer);
557
559
  this.snapshotTimer = setTimeout(() => {
558
560
  this.lastSnapshotImage = undefined;
559
- }, SNAPSHOTCACHETIMEOUT);
561
+ }, SNAPSHOT_CACHE_TIMEOUT);
560
562
  }
561
563
  }
562
564
 
@@ -1009,7 +1011,7 @@ export default class NestCamera extends HomeKitDevice {
1009
1011
  this.streamer !== undefined && this.streamer.stopEverything();
1010
1012
  this.streamer = undefined;
1011
1013
  }
1012
- if (deviceData.streaming_protocols.includes(PROTOCOLWEBRTC) === true && WebRTC !== undefined) {
1014
+ if (deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.WEBRTC) === true && WebRTC !== undefined) {
1013
1015
  this?.log?.debug?.('Using WebRTC streamer for "%s"', deviceData.description);
1014
1016
  this.streamer = new WebRTC(deviceData, {
1015
1017
  log: this.log,
@@ -1021,7 +1023,7 @@ export default class NestCamera extends HomeKitDevice {
1021
1023
  });
1022
1024
  }
1023
1025
 
1024
- if (deviceData.streaming_protocols.includes(PROTOCOLNEXUSTALK) === true && NexusTalk !== undefined) {
1026
+ if (deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.NEXUSTALK) === true && NexusTalk !== undefined) {
1025
1027
  this?.log?.debug?.('Using NexusTalk streamer for "%s"', deviceData.description);
1026
1028
  this.streamer = new NexusTalk(deviceData, {
1027
1029
  log: this.log,
@@ -7,11 +7,11 @@
7
7
  // Define our modules
8
8
  import HomeKitDevice from '../HomeKitDevice.js';
9
9
 
10
- const LOWBATTERYLEVEL = 10; // Low battery level percentage
10
+ const LOW_BATTERY_LEVEL = 10; // Low battery level percentage
11
11
 
12
12
  export default class NestProtect extends HomeKitDevice {
13
13
  static TYPE = 'Protect';
14
- static VERSION = '2025.06.11';
14
+ static VERSION = '2025.06.15';
15
15
 
16
16
  batteryService = undefined;
17
17
  smokeService = undefined;
@@ -79,7 +79,7 @@ export default class NestProtect extends HomeKitDevice {
79
79
  this.batteryService.updateCharacteristic(this.hap.Characteristic.BatteryLevel, deviceData.battery_level);
80
80
  this.batteryService.updateCharacteristic(
81
81
  this.hap.Characteristic.StatusLowBattery,
82
- deviceData.battery_level > LOWBATTERYLEVEL && deviceData.battery_health_state === 0
82
+ deviceData.battery_level > LOW_BATTERY_LEVEL && deviceData.battery_health_state === 0
83
83
  ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
84
84
  : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW,
85
85
  );
@@ -7,11 +7,11 @@
7
7
  // Define our modules
8
8
  import HomeKitDevice from '../HomeKitDevice.js';
9
9
 
10
- const LOWBATTERYLEVEL = 10; // Low battery level percentage
10
+ const LOW_BATTERY_LEVEL = 10; // Low battery level percentage
11
11
 
12
12
  export default class NestTemperatureSensor extends HomeKitDevice {
13
13
  static TYPE = 'TemperatureSensor';
14
- static VERSION = '2025.06.11';
14
+ static VERSION = '2025.06.15';
15
15
 
16
16
  batteryService = undefined;
17
17
  temperatureService = undefined;
@@ -70,7 +70,7 @@ export default class NestTemperatureSensor extends HomeKitDevice {
70
70
  this.batteryService.updateCharacteristic(this.hap.Characteristic.BatteryLevel, deviceData.battery_level);
71
71
  this.batteryService.updateCharacteristic(
72
72
  this.hap.Characteristic.StatusLowBattery,
73
- deviceData.battery_level > LOWBATTERYLEVEL
73
+ deviceData.battery_level > LOW_BATTERY_LEVEL
74
74
  ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
75
75
  : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW,
76
76
  );
@@ -13,14 +13,14 @@ import { fileURLToPath } from 'node:url';
13
13
  import HomeKitDevice from '../HomeKitDevice.js';
14
14
 
15
15
  // Define constants
16
- const LOWBATTERYLEVEL = 10; // Low battery level percentage
16
+ const LOW_BATTERY_LEVEL = 10; // Low battery level percentage
17
17
  const MIN_TEMPERATURE = 9; // Minimum temperature for Nest Thermostat
18
18
  const MAX_TEMPERATURE = 32; // Maximum temperature for Nest Thermostat
19
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
20
20
 
21
21
  export default class NestThermostat extends HomeKitDevice {
22
22
  static TYPE = 'Thermostat';
23
- static VERSION = '2025.06.12';
23
+ static VERSION = '2025.06.15';
24
24
 
25
25
  batteryService = undefined;
26
26
  occupancyService = undefined;
@@ -564,7 +564,7 @@ export default class NestThermostat extends HomeKitDevice {
564
564
  this.batteryService.updateCharacteristic(this.hap.Characteristic.BatteryLevel, deviceData.battery_level);
565
565
  this.batteryService.updateCharacteristic(
566
566
  this.hap.Characteristic.StatusLowBattery,
567
- deviceData.battery_level > LOWBATTERYLEVEL
567
+ deviceData.battery_level > LOW_BATTERY_LEVEL
568
568
  ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
569
569
  : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW,
570
570
  );