homebridge-nest-accfactory 0.3.4 → 0.3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
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.5 (2025/11/21)
6
+
7
+ - General code cleanup and stability improvements
8
+ - Fixed battery level reporting for Nest Thermostat (2020)
9
+ - Updated instructions for obtaining access token for Nest accounts
10
+ - Updated instructions for obtaining issue token and cookie for Google accounts
11
+ - Updated disclaimer in `README.md` to clarify support for official Homebridge installations only
12
+ - Added debug logging for thermostat mode and temperature changes received from outside of HomeKit [@MorelloCherry](https://github.com/MorelloCherry)
13
+
14
+ ### Known Issues
15
+
16
+ - The ip npm package has a known security advisory [GHSA-2p57-rm9w-gvfp](https://github.com/advisories/GHSA-2p57-rm9w-gvfp); this is used indirectly via the werift library
17
+
5
18
  ## v0.3.4 (2025/10/17)
6
19
 
7
20
  - General code cleanup and stability improvements
@@ -12,6 +25,10 @@ All notable changes to `homebridge-nest-accfactory` will be documented in this f
12
25
  - Fixed periodic camera snapshots when camera is turned off
13
26
  - Updated camera resource assets
14
27
 
28
+ ### Known Issues
29
+
30
+ - The ip npm package has a known security advisory [GHSA-2p57-rm9w-gvfp](https://github.com/advisories/GHSA-2p57-rm9w-gvfp); this is used indirectly via the werift library
31
+
15
32
  ## v0.3.3 (2025/08/23)
16
33
 
17
34
  - Refined timeout warnings for camera and doorbell snapshot capture
package/README.md CHANGED
@@ -36,32 +36,35 @@ The accessory supports connection to Nest using a Nest account AND/OR a Google (
36
36
 
37
37
  ### Nest Account
38
38
 
39
- If you have a Nest account, you will need to obtain an access token from the Nest web app. Simply go to https://home.nest.com in your browser and log in. Once that's done, go to https://home.nest.com/session in your browser, and you will see a long string that looks like this:
39
+ If you have a **Nest Account**, you’ll need to obtain an **access token** from the Nest web app.
40
40
 
41
- {"2fa_state":"enrolled","access_token":"XXX", ...}
41
+ 1. Go to [home.nest.com](https://home.nest.com) and click **Sign in with Nest**.
42
+ 2. After logging in, open [home.nest.com/session](https://home.nest.com/session).
43
+ 3. You’ll see a JSON string similar to:
44
+ ```json
45
+ {"2fa_state":"enrolled","access_token":"XXX", ...}
46
+ 4. Copy the value of **access_token** near the start (a long string beginning with `b`) and paste it into your Homebridge configuration.
47
+ - Ignore any other `access_token` entries further down the string.
42
48
 
43
- The value of "access_token" near the start of the string (the XXX) (a long sequence of letters, numbers and punctuation beginning with b) can be entered into the plugin-configuration within Homebridge
49
+ **Note:** Do **not** log out of [home.nest.com](https://home.nest.com), as this will invalidate your credentials.
44
50
 
45
- There may be other keys labelled access_token further along in the string - please ignore these.
51
+ ### Obtaining a Google cookie token for a Google Account (Safari)
46
52
 
47
- **Do not log out of home.nest.com, as this will invalidate your credentials. Just close the browser tab**
53
+ Google Accounts require an **"issueToken"** and **"cookie"**, which are unique to your account. You only need to do this once as long as you stay logged into your Google Account.
48
54
 
49
- ### Obtaining a Google cookie token for a Google Account
55
+ 1. Open Safari in a **Private Window**
56
+ 2. Enable the **Develop Menu** (Safari ▸ Settings ▸ Advanced ▸ check *Show Develop menu in menu bar*)
57
+ 3. Open **Develop ▸ Show Web Inspector**, then select the **Network** tab
58
+ 4. Ensure **Preserve Log** is checked
59
+ 5. In the filter box, type **issueToken**
60
+ 6. Go to [home.nest.com](https://home.nest.com) and click **Sign in with Google**
61
+ 7. After login, click the **iframerpc** network request
62
+ 8. In **Headers**, copy:
63
+ - **Request URL** - this is your **Issue Token**
64
+ - **cookie:** value under *Request Headers* - this is your **Cookie**
65
+ 9. Enter both into your Homebridge configuration
50
66
 
51
- Google Accounts require an "issueToken" and "cookie". The values of "issueToken" and "cookie" are specific to your Google Account. To get them, follow these steps (only needs to be done once, as long as you stay logged into your Google Account).
52
-
53
- 1. Open a Chrome browser tab in Incognito Mode
54
- 2. Open Developer Tools (View/Developer/Developer Tools).
55
- 3. Click on 'Network' tab. Make sure 'Preserve Log' is checked.
56
- 4. In the 'Filter' box, enter issueToken
57
- 5. Go to home.nest.com, and click 'Sign in with Google'. Log into your account.
58
- 6. One network call (beginning with iframerpc) will appear in the Dev Tools window. Click on it.
59
- 7. In the Headers tab, under General, copy the entire Request URL (beginning with https://accounts.google.com). This is your "Issue Token" which can be entered into the plugin-configuration within Homebridge.
60
- 8. In the 'Filter' box, enter oauth2/iframe
61
- 9. Several network calls will appear in the Dev Tools window. Click on the last iframe call.
62
- 10. In the Headers tab, under Request Headers, copy the entire cookie (include the whole string which is several lines long and has many field/value pairs - do not include the cookie: name). This is your "Cookie" which can be entered into the plugin-configuration within Homebridge.
63
-
64
- **Do not log out of home.nest.com, as this will invalidate your credentials. Just close the browser tab**
67
+ **Note:** Do **not** log out of [home.nest.com](https://home.nest.com), as this will invalidate your credentials.
65
68
 
66
69
  ## config.json configuration
67
70
 
@@ -154,4 +157,9 @@ By default, we look in /usr/local/bin for an ffmpeg binary, however, you can spe
154
157
 
155
158
  This is a personal hobby project, provided "as-is," with no warranty whatsoever, express or implied, including but not limited to warranties of merchantability or fitness for a particular purpose. Building and running this project is done entirely at your own risk.
156
159
 
157
- Please note that I am not affiliated with any companies, including but not limited to Google, Apple, or any other entities. The author of this project shall not be held liable for any damages or issues arising from its use. If you do encounter any problems with the source code, feel free to reach out, and we can discuss possible solutions
160
+ This plugin is only supported when used with **official Homebridge installations**.
161
+ Other platforms or forks such as **HOOBS**, **Home Assistant**, or similar derivatives are **not officially supported** and may not function as intended.
162
+
163
+ Please note that I am not affiliated with any companies, including but not limited to Google, Apple, or any other entities. The author of this project shall not be held liable for any damages or issues arising from its use.
164
+
165
+ If you encounter a problem with the source code, please [raise an issue](https://github.com/n0rt0nthec4t/homebridge-nest-accfactory/issues) on the project's **GitHub repository** so it can be reviewed and investigated.
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.09.08
4
+ // Code version 2025.10.21
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -27,7 +27,7 @@ function processConfig(config, log) {
27
27
  ? Number(config.options.elevation)
28
28
  : 0;
29
29
 
30
- // Controls what APIs we use, default is to use both Nest and protobuf APIs
30
+ // Controls what APIs we use, default is to use both Nest and Google APIs
31
31
  options.useNestAPI = config.options?.useNestAPI === true || config.options?.useNestAPI === undefined;
32
32
  options.useGoogleAPI = config.options?.useGoogleAPI === true || config.options?.useGoogleAPI === undefined;
33
33
 
@@ -143,15 +143,15 @@ function buildConnections(config) {
143
143
  Object.keys(config).forEach((key) => {
144
144
  let section = config[key];
145
145
 
146
- if (typeof section?.access_token === 'string' && section.access_token !== '') {
146
+ if (typeof section?.access_token === 'string' && section.access_token.trim() !== '') {
147
147
  let fieldTest = section?.fieldTest === true;
148
148
  connections[crypto.randomUUID()] = {
149
149
  name: key,
150
150
  type: ACCOUNT_TYPE.NEST,
151
151
  authorised: false,
152
152
  allowRetry: true,
153
- access_token: section.access_token,
154
- fieldTest,
153
+ access_token: section.access_token.trim(),
154
+ fieldTest: fieldTest,
155
155
  referer: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
156
156
  restAPIHost: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
157
157
  cameraAPIHost: fieldTest ? 'camera.home.ft.nest.com' : 'camera.home.nest.com',
@@ -161,9 +161,9 @@ function buildConnections(config) {
161
161
 
162
162
  if (
163
163
  typeof section?.issuetoken === 'string' &&
164
- section.issuetoken !== '' &&
164
+ section.issuetoken.trim() !== '' &&
165
165
  typeof section?.cookie === 'string' &&
166
- section.cookie !== ''
166
+ section.cookie.trim() !== ''
167
167
  ) {
168
168
  let fieldTest = section?.fieldTest === true;
169
169
  connections[crypto.randomUUID()] = {
@@ -171,9 +171,9 @@ function buildConnections(config) {
171
171
  type: ACCOUNT_TYPE.GOOGLE,
172
172
  authorised: false,
173
173
  allowRetry: true,
174
- issuetoken: section.issuetoken,
175
- cookie: section.cookie,
176
- fieldTest,
174
+ issuetoken: section.issuetoken.trim(),
175
+ cookie: section.cookie.trim(),
176
+ fieldTest: fieldTest,
177
177
  referer: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
178
178
  restAPIHost: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
179
179
  cameraAPIHost: fieldTest ? 'camera.home.ft.nest.com' : 'camera.home.nest.com',
@@ -28,7 +28,7 @@ import {
28
28
 
29
29
  export default class NestThermostat extends HomeKitDevice {
30
30
  static TYPE = 'Thermostat';
31
- static VERSION = '2025.09.08'; // Code version
31
+ static VERSION = '2025.11.20'; // Code version
32
32
 
33
33
  thermostatService = undefined;
34
34
  batteryService = undefined;
@@ -263,6 +263,7 @@ export default class NestThermostat extends HomeKitDevice {
263
263
  this.accessory.removeService(this.humidityService);
264
264
  this.accessory.removeService(this.fanService);
265
265
  this.accessory.removeService(this.dehumidifierService);
266
+ this.accessory.removeService(this.humidifierService);
266
267
  this.thermostatService = undefined;
267
268
  this.batteryService = undefined;
268
269
  this.occupancyService = undefined;
@@ -434,7 +435,31 @@ export default class NestThermostat extends HomeKitDevice {
434
435
  this?.log?.info?.('Heating/cooling setup on thermostat on "%s" has changed', deviceData.description);
435
436
  }
436
437
 
437
- // Update current mode temperatures
438
+ // Update current mode, temperatures and log any changes
439
+ if (deviceData.target_temperature_low !== this.deviceData.target_temperature_low) {
440
+ this.#logTemperatureChange(
441
+ 'Thermostat',
442
+ 'heating',
443
+ deviceData.target_temperature_low,
444
+ deviceData.hvac_mode?.toUpperCase?.().includes('ECO') === true,
445
+ deviceData.temperature_scale?.toUpperCase?.() === 'F' ? 'F' : 'C',
446
+ );
447
+ }
448
+
449
+ if (deviceData.target_temperature_high !== this.deviceData.target_temperature_high) {
450
+ this.#logTemperatureChange(
451
+ 'Thermostat',
452
+ 'cooling',
453
+ deviceData.target_temperature_high,
454
+ deviceData.hvac_mode?.toUpperCase?.().includes('ECO') === true,
455
+ deviceData.temperature_scale?.toUpperCase?.() === 'F' ? 'F' : 'C',
456
+ );
457
+ }
458
+
459
+ if (deviceData.hvac_mode.toUpperCase() !== this.deviceData.hvac_mode.toUpperCase()) {
460
+ this.#logModeChange('Thermostat', deviceData.hvac_mode);
461
+ }
462
+
438
463
  if (
439
464
  deviceData.can_heat === true &&
440
465
  (deviceData.hvac_mode.toUpperCase() === 'HEAT' || deviceData.hvac_mode.toUpperCase() === 'ECOHEAT')
@@ -786,7 +811,7 @@ export default class NestThermostat extends HomeKitDevice {
786
811
  this?.log?.info?.('Set temperature units on thermostat "%s" to "%s"', this.deviceData.description, unit === 'C' ? '°C' : '°F');
787
812
  }
788
813
 
789
- setMode(thermostatMode) {
814
+ async setMode(thermostatMode) {
790
815
  if (thermostatMode !== this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value) {
791
816
  // Work out based on the HomeKit requested mode, what can the thermostat really switch too
792
817
  // We may over-ride the requested HomeKit mode
@@ -847,9 +872,9 @@ export default class NestThermostat extends HomeKitDevice {
847
872
  mode = 'range';
848
873
  }
849
874
 
850
- this.message(HomeKitDevice.SET, { uuid: this.deviceData.nest_google_uuid, hvac_mode: mode });
875
+ await this.message(HomeKitDevice.SET, { uuid: this.deviceData.nest_google_uuid, hvac_mode: mode });
851
876
 
852
- this?.log?.info?.('Set mode on "%s" to "%s"', this.deviceData.description, mode);
877
+ this.#logModeChange('HomeKit', mode);
853
878
  }
854
879
  }
855
880
 
@@ -877,50 +902,49 @@ export default class NestThermostat extends HomeKitDevice {
877
902
  return currentMode;
878
903
  }
879
904
 
880
- setTemperature(characteristic, temperature) {
905
+ async setTemperature(characteristic, temperature) {
881
906
  if (typeof characteristic !== 'function' || typeof characteristic?.UUID !== 'string') {
882
907
  return;
883
908
  }
884
909
 
885
910
  let mode = this.thermostatService.getCharacteristic(this.hap.Characteristic.TargetHeatingCoolingState).value;
886
- let isEco = this.deviceData.hvac_mode?.toUpperCase?.().includes('ECO') === true;
887
- let scale = this.deviceData.temperature_scale?.toUpperCase?.() === 'F' ? 'F' : 'C';
888
- let tempDisplay = (scale === 'F' ? (temperature * 9) / 5 + 32 : temperature).toFixed(1);
889
- let tempUnit = scale === 'F' ? '°F' : '°C';
890
- let ecoPrefix = isEco ? 'eco mode ' : '';
891
-
892
911
  let targetKey = undefined;
893
912
  let modeLabel = '';
894
913
 
895
914
  if (
896
915
  characteristic.UUID === this.hap.Characteristic.TargetTemperature.UUID &&
897
- mode !== this.hap.Characteristic.TargetHeatingCoolingState.AUTO
916
+ mode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT
898
917
  ) {
899
- targetKey = 'target_temperature';
900
- modeLabel = mode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT ? 'heating' : 'cooling';
918
+ targetKey = 'target_temperature_low';
919
+ modeLabel = 'heating';
920
+ } else if (
921
+ characteristic.UUID === this.hap.Characteristic.TargetTemperature.UUID &&
922
+ mode === this.hap.Characteristic.TargetHeatingCoolingState.COOL
923
+ ) {
924
+ targetKey = 'target_temperature_high';
925
+ modeLabel = 'cooling';
901
926
  } else if (
902
927
  characteristic.UUID === this.hap.Characteristic.HeatingThresholdTemperature.UUID &&
903
- mode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO
928
+ (mode === this.hap.Characteristic.TargetHeatingCoolingState.HEAT || mode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO)
904
929
  ) {
905
930
  targetKey = 'target_temperature_low';
906
931
  modeLabel = 'heating';
907
932
  } else if (
908
933
  characteristic.UUID === this.hap.Characteristic.CoolingThresholdTemperature.UUID &&
909
- mode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO
934
+ (mode === this.hap.Characteristic.TargetHeatingCoolingState.COOL || mode === this.hap.Characteristic.TargetHeatingCoolingState.AUTO)
910
935
  ) {
911
936
  targetKey = 'target_temperature_high';
912
937
  modeLabel = 'cooling';
913
938
  }
914
939
 
915
940
  if (targetKey !== undefined) {
916
- this.message(HomeKitDevice.SET, { uuid: this.deviceData.nest_google_uuid, [targetKey]: temperature });
917
- this?.log?.info?.(
918
- 'Set %s%s temperature on "%s" to "%s %s"',
919
- ecoPrefix,
941
+ await this.message(HomeKitDevice.SET, { uuid: this.deviceData.nest_google_uuid, [targetKey]: temperature });
942
+ this.#logTemperatureChange(
943
+ 'HomeKit',
920
944
  modeLabel,
921
- this.deviceData.description,
922
- tempDisplay,
923
- tempUnit,
945
+ temperature,
946
+ this.deviceData.hvac_mode?.toUpperCase?.().includes('ECO') === true,
947
+ this.deviceData.temperature_scale?.toUpperCase?.() === 'F' ? 'F' : 'C',
924
948
  );
925
949
  }
926
950
 
@@ -1077,6 +1101,62 @@ export default class NestThermostat extends HomeKitDevice {
1077
1101
 
1078
1102
  return loadedModule;
1079
1103
  }
1104
+
1105
+ #logTemperatureChange(source, modeLabel, temperature, isEco, scale) {
1106
+ if (typeof temperature !== 'number') {
1107
+ return;
1108
+ }
1109
+
1110
+ let unitScale = typeof scale === 'string' ? scale.toUpperCase() : this.deviceData.temperature_scale?.toUpperCase?.();
1111
+ let isFahrenheit = unitScale === 'F';
1112
+ let tempDisplay = (isFahrenheit ? (temperature * 9) / 5 + 32 : temperature).toFixed(1);
1113
+ let tempUnit = isFahrenheit ? '°F' : '°C';
1114
+ let ecoPrefix = isEco === true ? 'eco mode ' : '';
1115
+
1116
+ modeLabel = modeLabel.charAt(0).toUpperCase() + modeLabel.slice(1).toLowerCase();
1117
+
1118
+ if (source === 'Thermostat') {
1119
+ this?.log?.debug?.(
1120
+ '%s%s temperature on "%s" changed to "%s %s"',
1121
+ ecoPrefix,
1122
+ modeLabel,
1123
+ this.deviceData.description,
1124
+ tempDisplay,
1125
+ tempUnit,
1126
+ );
1127
+ } else {
1128
+ this?.log?.info?.(
1129
+ 'Set %s%s temperature on "%s" to "%s %s"',
1130
+ ecoPrefix,
1131
+ modeLabel,
1132
+ this.deviceData.description,
1133
+ tempDisplay,
1134
+ tempUnit,
1135
+ );
1136
+ }
1137
+ }
1138
+
1139
+ #logModeChange(source, modeLabel) {
1140
+ if (typeof modeLabel !== 'string' || modeLabel.trim() === '') {
1141
+ return;
1142
+ }
1143
+
1144
+ modeLabel = modeLabel.charAt(0).toUpperCase() + modeLabel.slice(1).toLowerCase();
1145
+
1146
+ if (source === 'Thermostat') {
1147
+ this?.log?.debug?.(
1148
+ 'Mode on "%s" changed to "%s"',
1149
+ this.deviceData.description,
1150
+ modeLabel.toLowerCase().includes('range') === true ? 'Heat/Cool' : modeLabel,
1151
+ );
1152
+ } else {
1153
+ this?.log?.info?.(
1154
+ 'Set mode on "%s" to "%s"',
1155
+ this.deviceData.description,
1156
+ modeLabel.toLowerCase().includes('range') === true ? 'Heat/Cool' : modeLabel,
1157
+ );
1158
+ }
1159
+ }
1080
1160
  }
1081
1161
 
1082
1162
  // Function to process our RAW Nest or Google for this device type
@@ -1105,7 +1185,6 @@ export function processRawData(log, rawData, config, deviceType = undefined) {
1105
1185
  data.target_temperature = adjustTemperature(data.target_temperature, 'C', 'C', true);
1106
1186
  data.backplate_temperature = adjustTemperature(data.backplate_temperature, 'C', 'C', true);
1107
1187
  data.current_temperature = adjustTemperature(data.current_temperature, 'C', 'C', true);
1108
- data.battery_level = scaleValue(data.battery_level, 3.6, 3.9, 0, 100);
1109
1188
 
1110
1189
  processed = data;
1111
1190
  // eslint-disable-next-line no-unused-vars
@@ -1182,7 +1261,24 @@ export function processRawData(log, rawData, config, deviceType = undefined) {
1182
1261
  Array.isArray(value.value?.display?.thermostatState) === true && value.value.display.thermostatState.includes('bpd') === true;
1183
1262
  RESTTypeData.backplate_temperature = parseFloat(value.value.backplate_temperature.temperatureValue.temperature.value);
1184
1263
  RESTTypeData.current_temperature = parseFloat(value.value.current_temperature.temperatureValue.temperature.value);
1185
- RESTTypeData.battery_level = parseFloat(value.value.battery_voltage.batteryValue.batteryVoltage.value);
1264
+ if (value.value.device_info.typeName === 'google.resource.GoogleZirconium1Resource') {
1265
+ // Lower battery voltages for the "2020" mirror thermostat. Levels are a "guestimate" :-)
1266
+ RESTTypeData.battery_level = scaleValue(
1267
+ parseFloat(value.value.battery_voltage.batteryValue.batteryVoltage.value),
1268
+ 2.9,
1269
+ 3.15,
1270
+ 0,
1271
+ 100,
1272
+ );
1273
+ } else {
1274
+ RESTTypeData.battery_level = scaleValue(
1275
+ parseFloat(value.value.battery_voltage.batteryValue.batteryVoltage.value),
1276
+ 3.6,
1277
+ 3.9,
1278
+ 0,
1279
+ 100,
1280
+ );
1281
+ }
1186
1282
  RESTTypeData.online = value.value?.liveness?.status === 'LIVENESS_DEVICE_STATUS_ONLINE';
1187
1283
  RESTTypeData.leaf = value.value?.leaf?.active === true;
1188
1284
  RESTTypeData.can_cool =
@@ -1404,7 +1500,7 @@ export function processRawData(log, rawData, config, deviceType = undefined) {
1404
1500
  RESTTypeData.removed_from_base = value.value.nlclient_state.toUpperCase() === 'BPD';
1405
1501
  RESTTypeData.backplate_temperature = value.value.backplate_temperature;
1406
1502
  RESTTypeData.current_temperature = value.value.backplate_temperature;
1407
- RESTTypeData.battery_level = value.value.battery_level;
1503
+ RESTTypeData.battery_level = scaleValue(value.value.battery_level, 3.6, 3.9, 0, 100);
1408
1504
  RESTTypeData.online = rawData?.['track.' + value.value.serial_number]?.value?.online === true;
1409
1505
  RESTTypeData.leaf = value.value.leaf === true;
1410
1506
  RESTTypeData.has_humidifier = value.value.has_humidifier === true;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "homebridge-nest-accfactory",
3
3
  "displayName": "Nest Accfactory",
4
4
  "type": "module",
5
- "version": "0.3.4",
5
+ "version": "0.3.5",
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",
@@ -36,8 +36,8 @@
36
36
  ],
37
37
  "main": "dist/index.js",
38
38
  "engines": {
39
- "node": "^20.19.0 || ^22.12.0 || ^24.0.0",
40
- "homebridge": "^1.8.0 || ^2.0.0-beta.0"
39
+ "node": "^20 || ^22 || ^24",
40
+ "homebridge": "^1.11.1 || ^2.0.0-beta.0"
41
41
  },
42
42
  "files": [
43
43
  "LICENSE",
@@ -54,16 +54,16 @@
54
54
  "prepublishOnly": "npm run lint && npm run build"
55
55
  },
56
56
  "devDependencies": {
57
- "@eslint/js": "^9.37.0",
58
- "eslint": "^9.37.0",
59
- "@stylistic/eslint-plugin": "^5.4.0",
60
- "@types/node": "^24.8.1",
61
- "@typescript-eslint/parser": "^8.46.1",
57
+ "@eslint/js": "^9.39.1",
58
+ "eslint": "^9.39.1",
59
+ "@stylistic/eslint-plugin": "^5.6.1",
60
+ "@types/node": "^24.10.1",
61
+ "@typescript-eslint/parser": "^8.47.0",
62
62
  "prettier": "^3.6.2",
63
63
  "prettier-eslint": "^16.4.2",
64
64
  "copyfiles": "^2.4.1",
65
- "rimraf": "^6.0.1",
66
- "homebridge": "^2.0.0-beta.0"
65
+ "rimraf": "^6.1.0",
66
+ "homebridge": "^1.11.1 || ^2.0.0-beta.0"
67
67
  },
68
68
  "dependencies": {
69
69
  "@evan/opus": "^1.0.3",