@homebridge-plugins/homebridge-govee 11.25.0 → 11.26.1-beta.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 +17 -0
- package/lib/device/light.js +17 -5
- package/lib/device/sensor-leak.js +1 -1
- package/lib/platform.js +82 -13
- package/lib/utils/constants.js +2 -0
- package/lib/utils/device-capabilities.js +3 -1
- package/lib/utils/lang-en.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-govee` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v11.26.1 (Pending Release)
|
|
6
|
+
|
|
7
|
+
### Changes
|
|
8
|
+
|
|
9
|
+
- fix: report leak events relayed by a wifi gateway over AWS (#1276)
|
|
10
|
+
|
|
11
|
+
## v11.26.0 (2026-07-12)
|
|
12
|
+
|
|
13
|
+
### Changes
|
|
14
|
+
|
|
15
|
+
- feat: add support for the H609D Galaxy Light Projector (#1298)
|
|
16
|
+
- fix: never remove accessories based on a failed device fetch (#1264)
|
|
17
|
+
- fix: skip Govee app device groups from the OpenAPI device list (#1309)
|
|
18
|
+
- feat: add under-construction support for the H1370 fan (#1307)
|
|
19
|
+
- fix: use the segment-based BLE colour command for the H617E (#1290)
|
|
20
|
+
- fix: send the on command first so scenes from off apply their colour (#1277)
|
|
21
|
+
|
|
5
22
|
## v11.25.0 (2026-07-12)
|
|
6
23
|
|
|
7
24
|
### Changes
|
package/lib/device/light.js
CHANGED
|
@@ -250,8 +250,13 @@ export default class {
|
|
|
250
250
|
return
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
//
|
|
254
|
-
|
|
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
|
+
}
|
|
255
260
|
|
|
256
261
|
// Send the request to the platform sender function
|
|
257
262
|
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
@@ -316,9 +321,11 @@ export default class {
|
|
|
316
321
|
async internalBrightnessUpdate(value) {
|
|
317
322
|
try {
|
|
318
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)
|
|
319
326
|
const updateKeyBright = generateRandomString(5)
|
|
320
327
|
this.updateKeyBright = updateKeyBright
|
|
321
|
-
await sleep(350)
|
|
328
|
+
await sleep(this.cacheState !== 'on' ? 1050 : 350)
|
|
322
329
|
if (updateKeyBright !== this.updateKeyBright) {
|
|
323
330
|
return
|
|
324
331
|
}
|
|
@@ -367,9 +374,11 @@ export default class {
|
|
|
367
374
|
async internalColourUpdate(value, force = false) {
|
|
368
375
|
try {
|
|
369
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)
|
|
370
379
|
const updateKeyColour = generateRandomString(5)
|
|
371
380
|
this.updateKeyColour = updateKeyColour
|
|
372
|
-
await sleep(300)
|
|
381
|
+
await sleep(this.cacheState !== 'on' ? 1000 : 300)
|
|
373
382
|
if (updateKeyColour !== this.updateKeyColour) {
|
|
374
383
|
return
|
|
375
384
|
}
|
|
@@ -440,9 +449,12 @@ export default class {
|
|
|
440
449
|
async internalCTUpdate(value) {
|
|
441
450
|
try {
|
|
442
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)
|
|
443
455
|
const updateKeyCT = generateRandomString(5)
|
|
444
456
|
this.updateKeyCT = updateKeyCT
|
|
445
|
-
await sleep(300)
|
|
457
|
+
await sleep(this.cacheState !== 'on' ? 1000 : 300)
|
|
446
458
|
if (updateKeyCT !== this.updateKeyCT) {
|
|
447
459
|
return
|
|
448
460
|
}
|
|
@@ -67,7 +67,7 @@ export default class {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// Check to see if the provided leak status is different from the cached state
|
|
70
|
-
if (params.leakDetected !== this.cacheLeak) {
|
|
70
|
+
if (hasProperty(params, 'leakDetected') && params.leakDetected !== this.cacheLeak) {
|
|
71
71
|
// Leak status is different so update Homebridge with new values
|
|
72
72
|
this.cacheLeak = params.leakDetected
|
|
73
73
|
this.service.updateCharacteristic(this.hapChar.LeakDetected, this.cacheLeak ? 1 : 0)
|
package/lib/platform.js
CHANGED
|
@@ -19,6 +19,7 @@ import platformChars from './utils/custom-chars.js'
|
|
|
19
19
|
import { getDeviceCapabilities } from './utils/device-capabilities.js'
|
|
20
20
|
import eveChars from './utils/eve-chars.js'
|
|
21
21
|
import {
|
|
22
|
+
base64ToHex,
|
|
22
23
|
hasProperty,
|
|
23
24
|
parseDeviceId,
|
|
24
25
|
parseError,
|
|
@@ -654,6 +655,12 @@ export default class {
|
|
|
654
655
|
|
|
655
656
|
if (openApiDevices && openApiDevices.length > 0) {
|
|
656
657
|
openApiDevices.forEach((openApiDevice) => {
|
|
658
|
+
// Skip Govee app device groups as these are not real devices (#1309)
|
|
659
|
+
if (['BaseGroup', 'SameModeGroup'].includes(openApiDevice.model)) {
|
|
660
|
+
this.log.debug('[%s] %s.', openApiDevice.deviceName, platformLang.devIsGroup)
|
|
661
|
+
return
|
|
662
|
+
}
|
|
663
|
+
|
|
657
664
|
if (this.ignoredDevices.includes(openApiDevice.device)) {
|
|
658
665
|
return
|
|
659
666
|
}
|
|
@@ -717,18 +724,28 @@ export default class {
|
|
|
717
724
|
}
|
|
718
725
|
|
|
719
726
|
// Check for redundant Homebridge accessories
|
|
727
|
+
// Only treat 'not found in Govee' as a reason to remove an accessory when the
|
|
728
|
+
// configured cloud clients actually returned their device lists this session,
|
|
729
|
+
// otherwise a failed fetch would remove the accessory and destroy any HomeKit
|
|
730
|
+
// scenes and automations it is part of (#1264)
|
|
731
|
+
const cloudListsFetched = (!this.config.username || !!this.httpClient)
|
|
732
|
+
&& (!!this.config.openApiDisable || !this.config.apiKey || !!this.openApiClient)
|
|
720
733
|
devicesInHB.forEach((accessory) => {
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
|
|
725
|
-
if (
|
|
726
|
-
(!httpDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
727
|
-
&& !lanDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
728
|
-
&& !openApiDevices.some(el => el.device === accessory.context.gvDeviceId))
|
|
729
|
-
|| this.ignoredDevices.includes(accessory.context.gvDeviceId)
|
|
734
|
+
// Remove the accessory if it is now being ignored, or if it is a matter model
|
|
735
|
+
// and the user has enabled ignoreMatter (otherwise the cached accessory lingers
|
|
736
|
+
// even though the device is skipped during setup) (#1287)
|
|
737
|
+
const isIgnored = this.ignoredDevices.includes(accessory.context.gvDeviceId)
|
|
730
738
|
|| (this.config.ignoreMatter && platformConsts.matterModels.includes(accessory.context.gvModel))
|
|
731
|
-
|
|
739
|
+
|
|
740
|
+
// Also remove it if the device no longer exists in Govee, but never based on
|
|
741
|
+
// an incomplete cloud list, and never for LAN-only devices which may simply
|
|
742
|
+
// have missed a scan window (#1264)
|
|
743
|
+
const isMissing = !httpDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
744
|
+
&& !lanDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
745
|
+
&& !openApiDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
746
|
+
const isLanOnly = accessory.context.hasLanControl && !accessory.context.firmware
|
|
747
|
+
|
|
748
|
+
if (isIgnored || (isMissing && cloudListsFetched && !isLanOnly)) {
|
|
732
749
|
this.removeAccessory(accessory)
|
|
733
750
|
}
|
|
734
751
|
})
|
|
@@ -1356,6 +1373,14 @@ export default class {
|
|
|
1356
1373
|
}
|
|
1357
1374
|
}
|
|
1358
1375
|
|
|
1376
|
+
// Gateway subdevices (e.g. leak sensors paired to an H5044) include
|
|
1377
|
+
// their gateway id and slot number, which we need to match real-time
|
|
1378
|
+
// leak events relayed by the gateway over AWS (#1276)
|
|
1379
|
+
if (hasProperty(parsed, 'sno') && parsed.gatewayInfo?.device) {
|
|
1380
|
+
accessory.context.gatewaySno = parsed.sno
|
|
1381
|
+
accessory.context.gatewayDevice = parsed.gatewayInfo.device
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1359
1384
|
// Get a min and max temperature/humidity range to show in the homebridge-ui
|
|
1360
1385
|
if (hasProperty(parsed, 'temCali')) {
|
|
1361
1386
|
accessory.context.minTemp = parsed.temMin / 100
|
|
@@ -1506,6 +1531,13 @@ export default class {
|
|
|
1506
1531
|
if (platformConsts.models.sensorLeak.includes(device1.sku)) {
|
|
1507
1532
|
accessory.logDebug(`raw data: ${JSON.stringify({ ...parsedData, ...parsedSettings })}`)
|
|
1508
1533
|
|
|
1534
|
+
// Keep the gateway details up to date for matching real-time leak
|
|
1535
|
+
// events relayed by the gateway over AWS (#1276)
|
|
1536
|
+
if (hasProperty(parsedSettings, 'sno') && parsedSettings.gatewayInfo?.device) {
|
|
1537
|
+
accessory.context.gatewaySno = parsedSettings.sno
|
|
1538
|
+
accessory.context.gatewayDevice = parsedSettings.gatewayInfo.device
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1509
1541
|
// Leak Sensors - check to see of any warnings if the lastTime is above 0
|
|
1510
1542
|
let hasUnreadLeak = false
|
|
1511
1543
|
if (parsedData.lastTime > 0) {
|
|
@@ -1789,9 +1821,46 @@ export default class {
|
|
|
1789
1821
|
receiveUpdateAWS(payload) {
|
|
1790
1822
|
const accessoryUUID = this.api.hap.uuid.generate(payload.device)
|
|
1791
1823
|
const accessory = devicesInHB.get(accessoryUUID)
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1824
|
+
if (accessory) {
|
|
1825
|
+
this.receiveDeviceUpdate(accessory, {
|
|
1826
|
+
source: 'AWS',
|
|
1827
|
+
...payload,
|
|
1828
|
+
})
|
|
1829
|
+
return
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// No matching accessory - the message may be from a wifi gateway (e.g. H5044)
|
|
1833
|
+
// relaying an update for one of its subdevices, such as a leak sensor (#1276)
|
|
1834
|
+
this.receiveUpdateAWSGateway(payload)
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
receiveUpdateAWSGateway(payload) {
|
|
1838
|
+
(payload?.op?.command || []).forEach((command) => {
|
|
1839
|
+
let hexString
|
|
1840
|
+
try {
|
|
1841
|
+
hexString = base64ToHex(command)
|
|
1842
|
+
} catch {
|
|
1843
|
+
return
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// 0xEE34 is a gateway subdevice status, laid out as:
|
|
1847
|
+
// ee 34 [subdevice number] 02 00 64 1e 14 [seq] [unix time x4] [leak flags x4] 80 00 [checksum]
|
|
1848
|
+
if (!hexString.startsWith('ee34') || hexString.length < 36) {
|
|
1849
|
+
return
|
|
1850
|
+
}
|
|
1851
|
+
const subdeviceNo = Number.parseInt(hexString.substring(4, 6), 16)
|
|
1852
|
+
const leakDetected = hexString.substring(26, 34) !== '00000000'
|
|
1853
|
+
|
|
1854
|
+
// Match the update to the leak sensor with this gateway and slot number
|
|
1855
|
+
devicesInHB.forEach((accessory) => {
|
|
1856
|
+
if (
|
|
1857
|
+
accessory.context.gatewayDevice === payload.device
|
|
1858
|
+
&& accessory.context.gatewaySno === subdeviceNo
|
|
1859
|
+
&& platformConsts.models.sensorLeak.includes(accessory.context.gvModel)
|
|
1860
|
+
) {
|
|
1861
|
+
this.receiveDeviceUpdate(accessory, { source: 'AWS', leakDetected })
|
|
1862
|
+
}
|
|
1863
|
+
})
|
|
1795
1864
|
})
|
|
1796
1865
|
}
|
|
1797
1866
|
|
package/lib/utils/constants.js
CHANGED
|
@@ -219,6 +219,7 @@ export default {
|
|
|
219
219
|
'H6097',
|
|
220
220
|
'H6098',
|
|
221
221
|
'H6099',
|
|
222
|
+
'H609D',
|
|
222
223
|
'H60A0',
|
|
223
224
|
'H60A1',
|
|
224
225
|
'H60A4',
|
|
@@ -542,6 +543,7 @@ export default {
|
|
|
542
543
|
kettle: ['H7170', 'H7171', 'H7173', 'H7175', 'H717A'],
|
|
543
544
|
template: [
|
|
544
545
|
'H1162', // https://github.com/homebridge-plugins/homebridge-govee/issues/422
|
|
546
|
+
'H1370', // https://github.com/homebridge-plugins/homebridge-govee/issues/1307
|
|
545
547
|
'H1167', // https://github.com/homebridge-plugins/homebridge-govee/issues/1142
|
|
546
548
|
'H5010', // https://github.com/homebridge-plugins/homebridge-govee/issues/1235
|
|
547
549
|
'H5024', // https://github.com/homebridge-plugins/homebridge-govee/issues/835
|
|
@@ -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 },
|
package/lib/utils/lang-en.js
CHANGED
|
@@ -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',
|
package/package.json
CHANGED