@homebridge-plugins/homebridge-govee 11.34.2-beta.8 → 11.35.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 +6 -3
- package/lib/connection/ble.js +24 -2
- package/lib/utils/ble-protocol.js +28 -0
- package/lib/utils/device-context.js +33 -3
- package/lib/utils/lang-en.js +5 -0
- package/lib/utils/validation.js +17 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-govee` will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## v11.
|
|
5
|
+
## v11.35.0 (2026-08-09)
|
|
6
6
|
|
|
7
7
|
### Changed
|
|
8
8
|
|
|
@@ -19,10 +19,13 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
|
|
|
19
19
|
- fix: apply the openapi disable and refresh settings instead of asking for them to be removed
|
|
20
20
|
- fix: say once when a bluetooth reading arrives for an unknown device, not on every broadcast
|
|
21
21
|
- fix: reject a refresh time so large it would make the plugin poll every millisecond
|
|
22
|
-
- fix(H5054): don't let a slow leak warning block other sensors (#1348)
|
|
23
|
-
- fix: mark sensor batteries low at the configured threshold
|
|
22
|
+
- fix(H5054): don't let a slow leak warning block other sensors (#1348) (@danieloleary)
|
|
23
|
+
- fix: mark sensor batteries low at the configured threshold (#1349) (@danieloleary)
|
|
24
24
|
- feat: show whether the kettle is heating, keeping warm or idle (#1337)
|
|
25
25
|
- fix: give the H5106 a battery service, so its low battery setting does something
|
|
26
|
+
- fix: stop a light's broadcast being mistaken for an h5101 sensor reading
|
|
27
|
+
- fix: follow a device renamed in the govee app, instead of keeping its first name forever (#1344)
|
|
28
|
+
- fix: say why the bluetooth adapter would not start, rather than only that it timed out (#1344)
|
|
26
29
|
|
|
27
30
|
## v11.34.1 (2026-08-06)
|
|
28
31
|
|
package/lib/connection/ble.js
CHANGED
|
@@ -4,7 +4,7 @@ import process from 'node:process'
|
|
|
4
4
|
import btClient from '@stoprocent/noble'
|
|
5
5
|
|
|
6
6
|
import { buildHandshake, isHandshakeFrame, parseHandshakeResponse, V2Session } from '../utils/ble-crypto.js'
|
|
7
|
-
import { BLE_STAGE_TIMEOUTS, UUID } from '../utils/ble-protocol.js'
|
|
7
|
+
import { BLE_STAGE_TIMEOUTS, describeAdapterState, UUID } from '../utils/ble-protocol.js'
|
|
8
8
|
import { decodeAny } from '../utils/decode.js'
|
|
9
9
|
import { getDeviceCapabilities } from '../utils/device-capabilities.js'
|
|
10
10
|
import { base64ToHex, generateCodeFromHexValues, hexToTwoItems, sleep } from '../utils/functions.js'
|
|
@@ -42,6 +42,9 @@ export default class BLEConnection {
|
|
|
42
42
|
this.scanTimeoutId = null
|
|
43
43
|
this.isShuttingDown = false
|
|
44
44
|
|
|
45
|
+
// The power-on failure is said once, not every few minutes - see waitForPowerOn
|
|
46
|
+
this.powerOnReported = false
|
|
47
|
+
|
|
45
48
|
// Store event handler references for cleanup
|
|
46
49
|
this.eventHandlers = {
|
|
47
50
|
stateChange: null,
|
|
@@ -64,6 +67,11 @@ export default class BLEConnection {
|
|
|
64
67
|
this.btState = state
|
|
65
68
|
this.log.debug('[BLE] adapter state changed to: %s.', state)
|
|
66
69
|
|
|
70
|
+
// Someone has acted on it, so a later failure is worth saying again
|
|
71
|
+
if (state === 'poweredOn') {
|
|
72
|
+
this.powerOnReported = false
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
// If adapter loses power while operations are in progress, clean up
|
|
68
76
|
if (state !== 'poweredOn') {
|
|
69
77
|
this.handleAdapterPowerLoss()
|
|
@@ -224,7 +232,21 @@ export default class BLEConnection {
|
|
|
224
232
|
await btClient.waitForPoweredOnAsync(timeout)
|
|
225
233
|
return true
|
|
226
234
|
} catch (err) {
|
|
227
|
-
|
|
235
|
+
// Say what to do about it, from the state noble reported, rather than
|
|
236
|
+
// repeating its "timed out" message for every possible cause (#1344).
|
|
237
|
+
const advice = describeAdapterState(this.btState, platformLang)
|
|
238
|
+
const message = advice
|
|
239
|
+
? `${advice} [${this.btState}]`
|
|
240
|
+
: err.message
|
|
241
|
+
|
|
242
|
+
// The retry runs every few minutes, so say it once and then keep quiet -
|
|
243
|
+
// nothing changes until someone acts on it
|
|
244
|
+
if (this.powerOnReported) {
|
|
245
|
+
this.log.debug('[BLE] failed to power on adapter: %s.', message)
|
|
246
|
+
} else {
|
|
247
|
+
this.powerOnReported = true
|
|
248
|
+
this.log.warn('[BLE] failed to power on adapter: %s.', message)
|
|
249
|
+
}
|
|
228
250
|
return false
|
|
229
251
|
}
|
|
230
252
|
}
|
|
@@ -101,3 +101,31 @@ export const BLE_STAGE_TIMEOUTS = {
|
|
|
101
101
|
export const BLE_UPDATE_TIMEOUT = Object
|
|
102
102
|
.values(BLE_STAGE_TIMEOUTS)
|
|
103
103
|
.reduce((total, stage) => total + stage, 0)
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* What a noble adapter state means for the person reading the log.
|
|
107
|
+
*
|
|
108
|
+
* ⚠️ The plugin used to report only noble's own message — "Timeout waiting for
|
|
109
|
+
* Noble to be powered on" — for every one of these. That reads as a stuck
|
|
110
|
+
* adapter no matter which it is, and the three real causes need completely
|
|
111
|
+
* different fixes: a blocked radio, a missing capability, or no adapter at all.
|
|
112
|
+
* Working out which cost a reporter four days of back-and-forth (#1344), and it
|
|
113
|
+
* was knowable from the state noble had already reported.
|
|
114
|
+
*
|
|
115
|
+
* Returns undefined when the state does not point at a specific cause, so the
|
|
116
|
+
* caller can fall back to noble's own wording.
|
|
117
|
+
*/
|
|
118
|
+
export function describeAdapterState(state, lang) {
|
|
119
|
+
switch (state) {
|
|
120
|
+
case 'poweredOff':
|
|
121
|
+
return lang.blePowerBlocked
|
|
122
|
+
case 'unauthorized':
|
|
123
|
+
return lang.blePowerUnauthorized
|
|
124
|
+
case 'unsupported':
|
|
125
|
+
return lang.blePowerNoAdapter
|
|
126
|
+
case 'unknown':
|
|
127
|
+
return lang.blePowerUnknown
|
|
128
|
+
default:
|
|
129
|
+
return undefined
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import platformConsts from './constants.js'
|
|
2
2
|
import { getDeviceCapabilities } from './device-capabilities.js'
|
|
3
3
|
import { hasProperty } from './functions.js'
|
|
4
|
+
import platformLang from './lang-en.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Fills in everything a handler needs to know about a device before it is
|
|
@@ -69,9 +70,38 @@ export function applyDeviceContext(platform, accessory, device, deviceConf, join
|
|
|
69
70
|
accessory.context.useLanControl = true
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
// Keep the name in step with Govee - or, for a LAN-only device, with the
|
|
74
|
+
// label in the configuration.
|
|
75
|
+
//
|
|
76
|
+
// ⚠️ This used to run for LAN-only devices only, so every other device kept
|
|
77
|
+
// whatever name it was discovered under, forever. A name HAP complains about
|
|
78
|
+
// (brackets, say) could then not be cleared without deleting the cached
|
|
79
|
+
// accessory and losing its room and automations (#1344).
|
|
80
|
+
//
|
|
81
|
+
// ⚠️ This does NOT rename anything in the Home app. HomeKit fixes the name a
|
|
82
|
+
// user sees when the accessory is first added, and a rename made there never
|
|
83
|
+
// comes back to us either. What this fixes is the name Homebridge itself
|
|
84
|
+
// shows and, through the Name characteristic, the HAP warning.
|
|
85
|
+
//
|
|
86
|
+
// `updateDisplayName` is the supported way to do it - assigning to
|
|
87
|
+
// `displayName` sets only Homebridge's copy and leaves the HAP accessory
|
|
88
|
+
// behind. It arrived in Homebridge v1.10.0, and this plugin still supports
|
|
89
|
+
// ^1.6.0, hence the check.
|
|
90
|
+
if (device.deviceName && accessory.displayName !== device.deviceName) {
|
|
91
|
+
platform.log('[%s] %s [%s].', accessory.displayName, platformLang.devRenamed, device.deviceName)
|
|
92
|
+
|
|
93
|
+
if (typeof accessory.updateDisplayName === 'function') {
|
|
94
|
+
accessory.updateDisplayName(device.deviceName)
|
|
95
|
+
} else {
|
|
96
|
+
accessory.displayName = device.deviceName
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const hapChar = platform.api?.hap?.Characteristic
|
|
100
|
+
const infoService = accessory.getService?.(platform.api?.hap?.Service?.AccessoryInformation)
|
|
101
|
+
if (hapChar && infoService) {
|
|
102
|
+
infoService.updateCharacteristic(hapChar.Name, device.deviceName)
|
|
103
|
+
infoService.updateCharacteristic(hapChar.ConfiguredName, device.deviceName)
|
|
104
|
+
}
|
|
75
105
|
}
|
|
76
106
|
|
|
77
107
|
// See if we have extra HTTP client info for this device
|
package/lib/utils/lang-en.js
CHANGED
|
@@ -25,6 +25,10 @@ export default {
|
|
|
25
25
|
bleScanNoStart: 'could not start sync as',
|
|
26
26
|
bleScanNoStop: 'could not stop sync as',
|
|
27
27
|
bleScanUnknown: 'reading received for unknown accessory',
|
|
28
|
+
blePowerBlocked: 'the bluetooth adapter is switched off or blocked - check `rfkill list`, and `sudo rfkill unblock bluetooth` if it is blocked',
|
|
29
|
+
blePowerNoAdapter: 'no bluetooth adapter was found - check the adapter exists and the bluetooth service is running',
|
|
30
|
+
blePowerUnauthorized: 'not allowed to use the bluetooth adapter - node needs raw network access, which a node upgrade wipes: `sudo setcap cap_net_raw+eip $(readlink -f $(which node))`, then fully restart homebridge',
|
|
31
|
+
blePowerUnknown: 'the bluetooth adapter never reported its state - check the bluetooth service is running',
|
|
28
32
|
bleStart: 'scanning started',
|
|
29
33
|
bleStop: 'scanning stopped',
|
|
30
34
|
bleTimeout: 'BLE took too long',
|
|
@@ -82,6 +86,7 @@ export default {
|
|
|
82
86
|
devNotRet: 'skipping sync as device status not retrievable',
|
|
83
87
|
devNotUpdated: 'could not be updated as',
|
|
84
88
|
devRemove: 'has been removed from Homebridge',
|
|
89
|
+
devRenamed: 'has been renamed to',
|
|
85
90
|
disableClient: 'disabling client as',
|
|
86
91
|
disabledInConfig: 'disabled in config',
|
|
87
92
|
disabling: 'Disabling plugin',
|
package/lib/utils/validation.js
CHANGED
|
@@ -11,7 +11,23 @@ export function isHt5075(hex) { // Govee H5072/H5075
|
|
|
11
11
|
return hex.includes(h5075_uuid_rev) && hex.length === 16
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Govee H5100/H5101/H5102
|
|
15
|
+
//
|
|
16
|
+
// ⚠️ The length check matters as much as the marker. `0100` is only four hex
|
|
17
|
+
// digits, and plenty of Govee broadcasts happen to contain it - a light sending
|
|
18
|
+
// `4388ec00020100` did, which claimed the broadcast for the H5101 decoder, and
|
|
19
|
+
// that decoder reads sixteen characters and threw on fourteen (#1350). The
|
|
20
|
+
// throw surfaced as `error processing discovered peripheral` on every broadcast
|
|
21
|
+
// from a light that was working perfectly well.
|
|
22
|
+
//
|
|
23
|
+
// This is `>=` rather than the `===` its siblings use, deliberately: the
|
|
24
|
+
// decoder only ever reads the first sixteen characters, so anything at least
|
|
25
|
+
// that long decodes correctly today. Pinning an exact length would reject a
|
|
26
|
+
// longer payload from an H5111 or H5220 - both documented as sharing this
|
|
27
|
+
// format - and there is no sample of either to check that against.
|
|
28
|
+
export function isHt5101(hex) {
|
|
29
|
+
return hex.includes(h5101_uuid_rev) && hex.length >= 16
|
|
30
|
+
}
|
|
15
31
|
|
|
16
32
|
export function isHt5179(hex) { // Govee H5179
|
|
17
33
|
return hex.includes(h5179_uuid_rev) && hex.length === 22
|
package/package.json
CHANGED