@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 +29 -0
- package/eslint.config.js +0 -1
- package/lib/connection/http.js +2 -2
- package/lib/connection/openapi.js +5 -0
- package/lib/device/fan-H7111.js +26 -13
- package/lib/device/light.js +95 -5
- package/lib/device/purifier-H7123.js +10 -3
- package/lib/device/sensor-monitor.js +2 -0
- package/lib/device/sensor-thermo.js +2 -0
- package/lib/platform.js +27 -7
- package/lib/utils/constants.js +9 -0
- package/lib/utils/device-capabilities.js +3 -1
- package/lib/utils/lang-en.js +2 -1
- package/lib/utils/response-parser.js +5 -0
- package/package.json +13 -7
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,
|
package/lib/connection/http.js
CHANGED
|
@@ -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
|
package/lib/device/fan-H7111.js
CHANGED
|
@@ -118,7 +118,7 @@ export default class {
|
|
|
118
118
|
this.service
|
|
119
119
|
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
120
120
|
.setProps({
|
|
121
|
-
maxValue:
|
|
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
|
|
262
|
-
this.
|
|
263
|
-
|
|
264
|
-
|
|
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
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
this.
|
|
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 =
|
|
312
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed,
|
|
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
|
}
|
package/lib/device/light.js
CHANGED
|
@@ -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
|
-
//
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
})
|
package/lib/utils/constants.js
CHANGED
|
@@ -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 },
|
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',
|
|
@@ -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: '
|
|
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.
|
|
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.
|
|
59
|
+
"@homebridge/plugin-ui-utils": "^2.2.5",
|
|
60
60
|
"aws-iot-device-sdk": "^2.2.16",
|
|
61
|
-
"axios": "^1.
|
|
62
|
-
"mqtt": "^5.
|
|
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.
|
|
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.
|
|
71
|
+
"@stoprocent/noble": "^2.5.5"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@antfu/eslint-config": "^9.
|
|
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
|
}
|