@homebridge-plugins/homebridge-govee 11.24.0 → 11.26.0

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,35 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-govee` will be documented in this file.
4
4
 
5
+ ## v11.26.0 (2026-07-12)
6
+
7
+ ### Changes
8
+
9
+ - feat: add support for the H609D Galaxy Light Projector (#1298)
10
+ - fix: never remove accessories based on a failed device fetch (#1264)
11
+ - fix: skip Govee app device groups from the OpenAPI device list (#1309)
12
+ - feat: add under-construction support for the H1370 fan (#1307)
13
+ - fix: use the segment-based BLE colour command for the H617E (#1290)
14
+ - fix: send the on command first so scenes from off apply their colour (#1277)
15
+
16
+ ## v11.25.0 (2026-07-12)
17
+
18
+ ### Changes
19
+
20
+ - fix: parse water leak detection from the openapi response (#1276) (#1312) (@dev-d)
21
+ - feat: add support for the Edison Bulb H14C0 (#1302) (@starrlord)
22
+ - feat: add support for the H1270 ceiling light with independent zone control (#1306) (@x86txt)
23
+ - fix: H7111 HomeKit rotation speed percentage mapping (#1310) (@u2jrmw)
24
+ - fix: clamp H7123 air quality to HomeKit range (#1261)
25
+ - fix: hide matter devices when ignoreMatter is enabled (#1287)
26
+ - feat: add H70C5 to the matter exclusion list (#1300)
27
+ - fix: clearer error when Govee login returns no token
28
+ - chore: update actions/checkout to v7
29
+ - chore: dependency updates
30
+ - fix: show temperature on the HomeKit tile for sensors
31
+ - feat: add support for new light models
32
+ - chore: allow install scripts for native BLE/USB dependencies
33
+
5
34
  ## v11.24.0 (2026-05-31)
6
35
 
7
36
  ### Changes
package/eslint.config.js CHANGED
@@ -35,7 +35,6 @@ export default antfu(
35
35
  'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
36
36
  'style/quote-props': ['error', 'consistent-as-needed'],
37
37
  'test/no-only-tests': 'error',
38
- 'unicorn/no-useless-spread': 'error',
39
38
  'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }],
40
39
  },
41
40
  typescript: false,
@@ -59,7 +59,7 @@ export default class {
59
59
 
60
60
  // Check to see we got a response
61
61
  if (!res.data) {
62
- throw new Error(platformLang.noToken)
62
+ throw new Error(`${platformLang.noToken} (HTTP ${res.status})`)
63
63
  }
64
64
 
65
65
  // Handle 2FA requirement (status 454)
@@ -104,7 +104,7 @@ export default class {
104
104
  return await this.login()
105
105
  }
106
106
  }
107
- throw new Error(res.data.message || platformLang.noToken)
107
+ throw new Error(res.data.message || `${platformLang.noToken} (HTTP ${res.status})`)
108
108
  }
109
109
 
110
110
  // Make the token available in other functions
@@ -92,6 +92,8 @@ function getCapabilitiesFromPayload(data) {
92
92
  function getCapabilityValue(capability) {
93
93
  if (capability?.state && Object.hasOwn(capability.state, 'value')) {
94
94
  return capability.state.value
95
+ } else if (capability?.state && Array.isArray(capability.state)) {
96
+ return capability.state[0].value
95
97
  }
96
98
  return capability?.value
97
99
  }
@@ -560,6 +562,9 @@ export default class {
560
562
  message.capabilities.forEach((capability) => {
561
563
  const value = getCapabilityValue(capability)
562
564
  switch (capability?.instance) {
565
+ case 'bodyAppearedEvent':
566
+ normalized.leakDetected = !(Number(value) - 1)
567
+ break
563
568
  case 'online':
564
569
  normalized.online = !!value
565
570
  break
@@ -118,7 +118,7 @@ export default class {
118
118
  this.service
119
119
  .getCharacteristic(this.hapChar.RotationSpeed)
120
120
  .setProps({
121
- maxValue: this.autoSpeed,
121
+ maxValue: 100,
122
122
  minStep: 1,
123
123
  minValue: 0,
124
124
  unit: 'unitless',
@@ -176,6 +176,13 @@ export default class {
176
176
  return
177
177
  }
178
178
 
179
+ // HomeKit sends percentages
180
+ if (value >= 100) {
181
+ value = this.autoSpeed
182
+ } else if (value > 8) {
183
+ value = Math.max(1, Math.min(8, Math.round(value * 8 / 100)))
184
+ }
185
+
179
186
  // Don't continue if the new value is the same as before
180
187
  if (this.cacheSpeed === value) {
181
188
  return
@@ -258,10 +265,14 @@ export default class {
258
265
  } else {
259
266
  newSpeed = params.workMode.modeValue
260
267
  }
261
- if (Number.isFinite(newSpeed) && newSpeed >= 1 && newSpeed <= this.autoSpeed && this.cacheSpeed !== newSpeed) {
262
- this.cacheSpeed = newSpeed
263
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
264
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
268
+ if (Number.isFinite(newSpeed) && newSpeed >= 1 && newSpeed <= this.autoSpeed) {
269
+ const hkSpeed = newSpeed === this.autoSpeed ? 100 : Math.round((newSpeed / 8) * 100)
270
+
271
+ if (this.cacheSpeed !== hkSpeed) {
272
+ this.cacheSpeed = hkSpeed
273
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
274
+ this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
275
+ }
265
276
  }
266
277
  }
267
278
 
@@ -276,7 +287,7 @@ export default class {
276
287
  }
277
288
 
278
289
  // Check for some other scene/mode change
279
- (params.commands || []).forEach((command) => {
290
+ ;(params.commands || []).forEach((command) => {
280
291
  const hexString = base64ToHex(command)
281
292
  const hexParts = hexToTwoItems(hexString)
282
293
 
@@ -298,19 +309,21 @@ export default class {
298
309
  case '0501': {
299
310
  // Fan speed
300
311
  const newSpeed = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
301
- if (newSpeed >= 1 && newSpeed <= 8 && this.cacheSpeed !== newSpeed) {
302
- this.cacheSpeed = newSpeed
303
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
304
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
312
+ if (newSpeed >= 1 && newSpeed <= 8) {
313
+ const hkSpeed = Math.round((newSpeed / 8) * 100)
314
+
315
+ this.cacheSpeed = hkSpeed
316
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
317
+ this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
305
318
  }
306
319
  break
307
320
  }
308
321
  case '0500': {
309
322
  // Mode change (auto = 0x03)
310
323
  if (getTwoItemPosition(hexParts, 4) === '03' && this.cacheSpeed !== this.autoSpeed) {
311
- this.cacheSpeed = this.autoSpeed
312
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
313
- this.accessory.log(`${platformLang.curSpeed} [auto]`)
324
+ this.cacheSpeed = 100
325
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
326
+ this.accessory.log(`${platformLang.curSpeed} [auto -> 100%]`)
314
327
  }
315
328
  break
316
329
  }
@@ -198,6 +198,33 @@ export default class {
198
198
  this.accessory.context.adaptiveLighting = true
199
199
  }
200
200
 
201
+ // Some lights (e.g. the H1270 Ceiling Light Ultra) have two independently
202
+ // controllable zones - a main/bottom panel and a background/top light. Govee
203
+ // exposes these as OpenAPI toggle capabilities, so add a switch for each zone
204
+ // the device reports. These are only controllable via the OpenAPI connection.
205
+ this.zoneServices = {}
206
+ this.zoneCache = {}
207
+ const zoneToggleDefs = [
208
+ { instance: 'mainLightToggle', name: 'Main Light' },
209
+ { instance: 'backgroundLightToggle', name: 'Background Light' },
210
+ ]
211
+ const openApiCaps = accessory.context.openApiCapabilities || {}
212
+ zoneToggleDefs.forEach(({ instance, name }) => {
213
+ const existingService = this.accessory.getService(name)
214
+ if (accessory.context.useOpenApiControl && openApiCaps[instance]) {
215
+ const service = existingService
216
+ || this.accessory.addService(this.hapServ.Switch, name, instance)
217
+ service
218
+ .getCharacteristic(this.hapChar.On)
219
+ .onSet(async value => this.internalZoneUpdate(instance, name, value))
220
+ this.zoneServices[instance] = service
221
+ this.zoneCache[instance] = service.getCharacteristic(this.hapChar.On).value ? 1 : 0
222
+ } else if (existingService) {
223
+ // Capability no longer available (or OpenAPI disabled) so remove the switch
224
+ this.accessory.removeService(existingService)
225
+ }
226
+ })
227
+
201
228
  // Output the customised options to the log
202
229
  const useAwsControl = accessory.context.useAwsControl ? 'enabled' : 'disabled'
203
230
  const useBleControl = accessory.context.useBleControl ? 'enabled' : 'disabled'
@@ -223,8 +250,13 @@ export default class {
223
250
  return
224
251
  }
225
252
 
226
- // Await slightly longer than brightness and colour so on/off is sent last
227
- await sleep(400)
253
+ // Send off after colour/brightness (a colour command can implicitly power a
254
+ // bulb back on), but send on straight away so the bulb is awake in time to
255
+ // receive the colour and brightness a HomeKit scene sends alongside it, as
256
+ // some models ignore these commands while switched off (#1277)
257
+ if (newValue === 'off') {
258
+ await sleep(400)
259
+ }
228
260
 
229
261
  // Send the request to the platform sender function
230
262
  await this.platform.sendDeviceUpdate(this.accessory, {
@@ -249,12 +281,51 @@ export default class {
249
281
  }
250
282
  }
251
283
 
284
+ async internalZoneUpdate(instance, name, value) {
285
+ const service = this.zoneServices[instance]
286
+ try {
287
+ const newValue = value ? 1 : 0
288
+
289
+ // Don't continue if the new value is the same as before
290
+ if (this.zoneCache[instance] === newValue) {
291
+ return
292
+ }
293
+
294
+ // These toggles are only controllable via the OpenAPI (cloud) connection
295
+ await this.platform.sendDeviceUpdate(this.accessory, {
296
+ cmd: 'openApi',
297
+ openApi: {
298
+ instance,
299
+ capabilityType: 'devices.capabilities.toggle',
300
+ value: newValue,
301
+ },
302
+ })
303
+
304
+ // Cache the new state and log
305
+ this.zoneCache[instance] = newValue
306
+ this.accessory.log(`[${name}] ${platformLang.curState} [${value ? 'on' : 'off'}]`)
307
+ } catch (err) {
308
+ // Catch any errors during the process
309
+ this.accessory.logWarn(`[${name}] ${platformLang.devNotUpdated} ${parseError(err)}`)
310
+
311
+ // Throw a 'no response' error and revert this after 2 seconds
312
+ setTimeout(() => {
313
+ if (service) {
314
+ service.updateCharacteristic(this.hapChar.On, this.zoneCache[instance] === 1)
315
+ }
316
+ }, 2000)
317
+ throw new this.hapErr(-70402)
318
+ }
319
+ }
320
+
252
321
  async internalBrightnessUpdate(value) {
253
322
  try {
254
323
  // This acts like a debounce function when endlessly sliding the brightness scale
324
+ // If the light is off (e.g. a HomeKit scene turning it on), wait longer so the
325
+ // on command sent by internalStateUpdate has time to wake the bulb first (#1277)
255
326
  const updateKeyBright = generateRandomString(5)
256
327
  this.updateKeyBright = updateKeyBright
257
- await sleep(350)
328
+ await sleep(this.cacheState !== 'on' ? 1050 : 350)
258
329
  if (updateKeyBright !== this.updateKeyBright) {
259
330
  return
260
331
  }
@@ -303,9 +374,11 @@ export default class {
303
374
  async internalColourUpdate(value, force = false) {
304
375
  try {
305
376
  // This acts like a debounce function when endlessly sliding the colour wheel
377
+ // If the light is off (e.g. a HomeKit scene turning it on), wait longer so the
378
+ // on command sent by internalStateUpdate has time to wake the bulb first (#1277)
306
379
  const updateKeyColour = generateRandomString(5)
307
380
  this.updateKeyColour = updateKeyColour
308
- await sleep(300)
381
+ await sleep(this.cacheState !== 'on' ? 1000 : 300)
309
382
  if (updateKeyColour !== this.updateKeyColour) {
310
383
  return
311
384
  }
@@ -376,9 +449,12 @@ export default class {
376
449
  async internalCTUpdate(value) {
377
450
  try {
378
451
  // This acts like a debounce function when endlessly sliding the colour wheel
452
+ // If the light is off (e.g. a HomeKit scene turning it on), wait longer so the
453
+ // on command sent by internalStateUpdate has time to wake the bulb first, and
454
+ // so the 'skip when off' check below sees the updated state (#1277)
379
455
  const updateKeyCT = generateRandomString(5)
380
456
  this.updateKeyCT = updateKeyCT
381
- await sleep(300)
457
+ await sleep(this.cacheState !== 'on' ? 1000 : 300)
382
458
  if (updateKeyCT !== this.updateKeyCT) {
383
459
  return
384
460
  }
@@ -613,5 +689,19 @@ export default class {
613
689
  }
614
690
  }
615
691
  }
692
+
693
+ // Update any independently-controllable light zones (e.g. main/background)
694
+ if (params.toggles) {
695
+ Object.entries(this.zoneServices).forEach(([instance, service]) => {
696
+ if (hasProperty(params.toggles, instance)) {
697
+ const newValue = params.toggles[instance] ? 1 : 0
698
+ if (this.zoneCache[instance] !== newValue) {
699
+ this.zoneCache[instance] = newValue
700
+ service.updateCharacteristic(this.hapChar.On, newValue === 1)
701
+ this.accessory.log(`[${service.displayName}] ${platformLang.curState} [${newValue === 1 ? 'on' : 'off'}]`)
702
+ }
703
+ }
704
+ })
705
+ }
616
706
  }
617
707
  }
@@ -74,10 +74,11 @@ export default class {
74
74
  }
75
75
 
76
76
  this.airValue2Label = {
77
+ 0: 'unknown',
77
78
  1: 'excellent',
78
79
  2: 'good',
79
80
  3: 'moderate',
80
- 4: 'poor',
81
+ 5: 'poor',
81
82
  }
82
83
 
83
84
  // Add the purifier service if it doesn't already exist
@@ -469,13 +470,19 @@ export default class {
469
470
  }
470
471
  case 'aa19': {
471
472
  // Check air quality reading (i.e. 1=green, 2=blue, 3=yellow, 4=red)
472
- // Cache will be in {1, 2, 3, 5} which relates to Govee {1, 2, 3, 4}
473
+ // Govee reports 1-4 (excellent→poor); HomeKit AirQuality accepts 0-5.
474
+ // Map Govee's 'poor' (and any higher/unexpected reading) to HomeKit's
475
+ // Poor (5), and treat anything invalid as Unknown (0), so HomeKit is
476
+ // never sent an out-of-range value (#1261).
473
477
  let newQual = Number.parseInt(getTwoItemPosition(hexParts, 5), 16)
474
- if (newQual === 4) {
478
+ if (!Number.isInteger(newQual) || newQual < 1) {
479
+ newQual = 0
480
+ } else if (newQual >= 4) {
475
481
  newQual = 5
476
482
  }
477
483
 
478
484
  if (newQual !== this.cacheAir) {
485
+ this.cacheAir = newQual
479
486
  this.airService.updateCharacteristic(this.hapChar.AirQuality, newQual)
480
487
  this.accessory.log(`${platformLang.curAirQual} [${this.airValue2Label[newQual]}]`)
481
488
  }
@@ -29,6 +29,8 @@ export default class {
29
29
  // Add the temperature service if it doesn't already exist
30
30
  this.tempService = this.accessory.getService(this.hapServ.TemperatureSensor)
31
31
  || this.accessory.addService(this.hapServ.TemperatureSensor)
32
+ // Mark it as the primary service so the HomeKit tile shows the temperature
33
+ this.tempService.setPrimaryService(true)
32
34
  this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
33
35
  this.updateCache()
34
36
 
@@ -33,6 +33,8 @@ export default class {
33
33
  // Add the temperature service if it doesn't already exist
34
34
  this.tempService = this.accessory.getService(this.hapServ.TemperatureSensor)
35
35
  || this.accessory.addService(this.hapServ.TemperatureSensor)
36
+ // Mark it as the primary service so the HomeKit tile shows the temperature
37
+ this.tempService.setPrimaryService(true)
36
38
  this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
37
39
 
38
40
  // Add the battery service if it doesn't already exist
package/lib/platform.js CHANGED
@@ -654,6 +654,12 @@ export default class {
654
654
 
655
655
  if (openApiDevices && openApiDevices.length > 0) {
656
656
  openApiDevices.forEach((openApiDevice) => {
657
+ // Skip Govee app device groups as these are not real devices (#1309)
658
+ if (['BaseGroup', 'SameModeGroup'].includes(openApiDevice.model)) {
659
+ this.log.debug('[%s] %s.', openApiDevice.deviceName, platformLang.devIsGroup)
660
+ return
661
+ }
662
+
657
663
  if (this.ignoredDevices.includes(openApiDevice.device)) {
658
664
  return
659
665
  }
@@ -717,14 +723,28 @@ export default class {
717
723
  }
718
724
 
719
725
  // Check for redundant Homebridge accessories
726
+ // Only treat 'not found in Govee' as a reason to remove an accessory when the
727
+ // configured cloud clients actually returned their device lists this session,
728
+ // otherwise a failed fetch would remove the accessory and destroy any HomeKit
729
+ // scenes and automations it is part of (#1264)
730
+ const cloudListsFetched = (!this.config.username || !!this.httpClient)
731
+ && (!!this.config.openApiDisable || !this.config.apiKey || !!this.openApiClient)
720
732
  devicesInHB.forEach((accessory) => {
721
- // If the accessory doesn't exist in Govee then remove it
722
- if (
723
- (!httpDevices.some(el => el.device === accessory.context.gvDeviceId)
724
- && !lanDevices.some(el => el.device === accessory.context.gvDeviceId)
725
- && !openApiDevices.some(el => el.device === accessory.context.gvDeviceId))
726
- || this.ignoredDevices.includes(accessory.context.gvDeviceId)
727
- ) {
733
+ // Remove the accessory if it is now being ignored, or if it is a matter model
734
+ // and the user has enabled ignoreMatter (otherwise the cached accessory lingers
735
+ // even though the device is skipped during setup) (#1287)
736
+ const isIgnored = this.ignoredDevices.includes(accessory.context.gvDeviceId)
737
+ || (this.config.ignoreMatter && platformConsts.matterModels.includes(accessory.context.gvModel))
738
+
739
+ // Also remove it if the device no longer exists in Govee, but never based on
740
+ // an incomplete cloud list, and never for LAN-only devices which may simply
741
+ // have missed a scan window (#1264)
742
+ const isMissing = !httpDevices.some(el => el.device === accessory.context.gvDeviceId)
743
+ && !lanDevices.some(el => el.device === accessory.context.gvDeviceId)
744
+ && !openApiDevices.some(el => el.device === accessory.context.gvDeviceId)
745
+ const isLanOnly = accessory.context.hasLanControl && !accessory.context.firmware
746
+
747
+ if (isIgnored || (isMissing && cloudListsFetched && !isLanOnly)) {
728
748
  this.removeAccessory(accessory)
729
749
  }
730
750
  })
@@ -128,7 +128,9 @@ export default {
128
128
 
129
129
  models: {
130
130
  rgb: [
131
+ 'H1270', // Ceiling Light Ultra
131
132
  'H1401',
133
+ 'H14C0', // https://github.com/homebridge-plugins/homebridge-govee/issues/1301
132
134
  'H1630',
133
135
  'H16B0', // https://github.com/homebridge-plugins/homebridge-govee/issues/1278
134
136
  'H16C0', // https://github.com/homebridge-plugins/homebridge-govee/issues/1286
@@ -217,6 +219,7 @@ export default {
217
219
  'H6097',
218
220
  'H6098',
219
221
  'H6099',
222
+ 'H609D',
220
223
  'H60A0',
221
224
  'H60A1',
222
225
  'H60A4',
@@ -391,6 +394,7 @@ export default {
391
394
  'H7024',
392
395
  'H7025',
393
396
  'H7026',
397
+ 'H7027', // https://github.com/homebridge-plugins/homebridge-govee/issues/1305
394
398
  'H7028',
395
399
  'H7029',
396
400
  'H702A',
@@ -406,6 +410,7 @@ export default {
406
410
  'H703B',
407
411
  'H7041',
408
412
  'H7042',
413
+ 'H7046', // https://github.com/homebridge-plugins/homebridge-govee/issues/1297
409
414
  'H7050',
410
415
  'H7051',
411
416
  'H7052',
@@ -469,6 +474,7 @@ export default {
469
474
  'H801C',
470
475
  'H8022',
471
476
  'H8025',
477
+ 'H802A', // https://github.com/homebridge-plugins/homebridge-govee/issues/1295
472
478
  'H805A',
473
479
  'H805B',
474
480
  'H805C',
@@ -537,6 +543,7 @@ export default {
537
543
  kettle: ['H7170', 'H7171', 'H7173', 'H7175', 'H717A'],
538
544
  template: [
539
545
  'H1162', // https://github.com/homebridge-plugins/homebridge-govee/issues/422
546
+ 'H1370', // https://github.com/homebridge-plugins/homebridge-govee/issues/1307
540
547
  'H1167', // https://github.com/homebridge-plugins/homebridge-govee/issues/1142
541
548
  'H5010', // https://github.com/homebridge-plugins/homebridge-govee/issues/1235
542
549
  'H5024', // https://github.com/homebridge-plugins/homebridge-govee/issues/835
@@ -558,6 +565,7 @@ export default {
558
565
 
559
566
  matterModels: [
560
567
  'H1401',
568
+ 'H14C0',
561
569
  'H3200',
562
570
  'H5085',
563
571
  'H600B',
@@ -590,6 +598,7 @@ export default {
590
598
  'H7075',
591
599
  'H7094',
592
600
  'H70C4',
601
+ 'H70C5',
593
602
  'H8015',
594
603
  ],
595
604
 
@@ -14,12 +14,14 @@ const modelOverrides = {
14
14
  H6102: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
15
15
  H613B: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
16
16
  H613D: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
17
- H617E: { bleColorCmd: [COLOR_SUB.RGB_ALT], bleBrightnessScale: 0x64 },
18
17
 
19
18
  // BLE color sub-command: 0x15 0x01 (extended) with trailing bytes
20
19
  H6053: { bleColorCmd: COLOR_SUB.RGB_EXTENDED, bleColorCmdSuffix: [0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F] },
21
20
  H6072: { bleColorCmd: COLOR_SUB.RGB_EXTENDED, bleColorCmdSuffix: [0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F] },
22
21
  H6199: { bleColorCmd: COLOR_SUB.RGB_EXTENDED, bleColorCmdSuffix: [0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F] },
22
+ // H617E is an RGBIC strip which ignores the 0x0D command - it needs the
23
+ // segment-based extended format, as seen in its own TTR rule data (#1290)
24
+ H617E: { bleColorCmd: COLOR_SUB.RGB_EXTENDED, bleColorCmdSuffix: [0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F], bleBrightnessScale: 0x64 },
23
25
 
24
26
  // AWS outlet uses 17/16 for on/off instead of 1/0
25
27
  H5080: { awsPowerOn: 17, awsPowerOff: 16 },
@@ -68,6 +68,7 @@ export default {
68
68
  curWatt: 'current wattage',
69
69
  devAdd: 'has been added to Homebridge',
70
70
  devInit: 'initialised with id',
71
+ devIsGroup: 'is a Govee app device group so will not be added to Homebridge',
71
72
  devInitOpts: 'initialising with options',
72
73
  devMaySupp: 'is not currently supported, do create a Github issue with the following info',
73
74
  devNoBlePackage: 'will not be added as BLE is disabled in the config',
@@ -113,7 +114,7 @@ export default {
113
114
  notBLESent: 'not using BLE connection as',
114
115
  notLANSent: 'not using LAN connection as',
115
116
  noOpenssl: 'See https://github.com/homebridge-plugins/homebridge-govee/wiki/OpenSSL for more info about installing OpenSSL',
116
- noToken: 'no data received from Govee server whilst obtaining token',
117
+ noToken: 'could not obtain an account token from Govee - the account may need attention in the Govee Home app (e.g. an email verification code or accepting updated terms), or it may be served by a region this plugin does not support. If you only use Bluetooth or LAN control then this cloud login is not required',
117
118
  twoFARequired: '2FA verification required - a code has been sent to your email, please add it to the "code" field in your plugin config and restart Homebridge',
118
119
  twoFACodeInvalid: '2FA verification code was provided but login still failed - please check the code is correct and has not expired',
119
120
  noTokenExists: 'no account token has been retrieved',
@@ -77,6 +77,11 @@ export function parseDeviceUpdate(params, context) {
77
77
  // LEAK DETECTED
78
78
  if (hasProperty(params, 'leakDetected')) {
79
79
  data.leakDetected = params.leakDetected
80
+ } else if (params?.state?.leakDetected) {
81
+ data.leakDetected = params.state.leakDetected
82
+ }
83
+ if (params?.state && hasProperty(params.state, 'online')) {
84
+ data.online = params.state.online
80
85
  }
81
86
 
82
87
  // CURRENT TEMPERATURE
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@homebridge-plugins/homebridge-govee",
3
3
  "alias": "Govee",
4
4
  "type": "module",
5
- "version": "11.24.0",
5
+ "version": "11.26.0",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",
@@ -56,21 +56,27 @@
56
56
  "postinstall": "patch-package"
57
57
  },
58
58
  "dependencies": {
59
- "@homebridge/plugin-ui-utils": "^2.2.4",
59
+ "@homebridge/plugin-ui-utils": "^2.2.5",
60
60
  "aws-iot-device-sdk": "^2.2.16",
61
- "axios": "^1.16.1",
62
- "mqtt": "^5.12.1",
61
+ "axios": "^1.18.1",
62
+ "mqtt": "^5.15.2",
63
63
  "node-persist": "^4.0.4",
64
64
  "node-rsa": "^2.0.0",
65
- "p-queue": "^9.3.0",
65
+ "p-queue": "^9.3.1",
66
66
  "patch-package": "^8.0.1",
67
67
  "pem": "^1.14.8"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "@stoprocent/bluetooth-hci-socket": "^2.2.6",
71
- "@stoprocent/noble": "^2.5.3"
71
+ "@stoprocent/noble": "^2.5.5"
72
72
  },
73
73
  "devDependencies": {
74
- "@antfu/eslint-config": "^9.0.0"
74
+ "@antfu/eslint-config": "^9.1.0"
75
+ },
76
+ "allowScripts": {
77
+ "@stoprocent/bluetooth-hci-socket@2.2.6": true,
78
+ "@stoprocent/noble@2.5.5": true,
79
+ "usb@2.18.0": true,
80
+ "@serialport/bindings-cpp@12.0.1": true
75
81
  }
76
82
  }