@homebridge-plugins/homebridge-govee 11.32.0-beta.26 → 11.32.0-beta.27
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 +1 -0
- package/lib/connection/ble.js +8 -5
- package/lib/platform.js +5 -1
- package/lib/utils/ble-protocol.js +32 -0
- package/lib/utils/ble-timeouts.test.js +44 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,7 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
|
|
|
30
30
|
- fix(fans): rebuild the fan service once when it was cached under the old speed scale, so the top of the slider stops setting the slowest speed (#1326) (@cwhett)
|
|
31
31
|
- fix: show the H5089 night light tile again, but refuse control commands, so it reports the real state instead of quietly ignoring taps (#1323)
|
|
32
32
|
- fix(ice-makers): stop the home app showing an ice size of 0 when one has never been set (#1329) (@cwhett)
|
|
33
|
+
- fix(ble): size the update queue timeout from the connection stages, so a slow device still has time to send its command (#1328)
|
|
33
34
|
|
|
34
35
|
## v11.31.0 (2026-07-24)
|
|
35
36
|
|
package/lib/connection/ble.js
CHANGED
|
@@ -3,7 +3,7 @@ import process from 'node:process'
|
|
|
3
3
|
|
|
4
4
|
import btClient from '@stoprocent/noble'
|
|
5
5
|
|
|
6
|
-
import { UUID } from '../utils/ble-protocol.js'
|
|
6
|
+
import { BLE_STAGE_TIMEOUTS, UUID } from '../utils/ble-protocol.js'
|
|
7
7
|
import { decodeAny } from '../utils/decode.js'
|
|
8
8
|
import { base64ToHex, generateCodeFromHexValues, hexToTwoItems, sleep } from '../utils/functions.js'
|
|
9
9
|
import platformLang from '../utils/lang-en.js'
|
|
@@ -15,8 +15,11 @@ process.env.NOBLE_REPORT_ALL_HCI_EVENTS = '1' // needed on Linux including Raspb
|
|
|
15
15
|
|
|
16
16
|
const H5075_UUID = 'ec88'
|
|
17
17
|
const H5101_UUID = '0001'
|
|
18
|
-
|
|
19
|
-
const
|
|
18
|
+
|
|
19
|
+
const POWER_ON_TIMEOUT = BLE_STAGE_TIMEOUTS.powerOn
|
|
20
|
+
const CONNECTION_TIMEOUT = BLE_STAGE_TIMEOUTS.connect
|
|
21
|
+
const WRITE_TIMEOUT = BLE_STAGE_TIMEOUTS.write
|
|
22
|
+
const SETTLE_DELAY = BLE_STAGE_TIMEOUTS.settle
|
|
20
23
|
|
|
21
24
|
/*
|
|
22
25
|
The necessary commands to send and functions are taken from and credit to:
|
|
@@ -169,7 +172,7 @@ export default class BLEConnection {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
async waitForPowerOn(timeout =
|
|
175
|
+
async waitForPowerOn(timeout = POWER_ON_TIMEOUT) {
|
|
173
176
|
if (this.btState === 'poweredOn') {
|
|
174
177
|
return true
|
|
175
178
|
}
|
|
@@ -240,7 +243,7 @@ export default class BLEConnection {
|
|
|
240
243
|
// Write without response then allow time for the BLE controller to transmit before disconnecting
|
|
241
244
|
await this.writeWithTimeout(characteristic, finalBuffer, WRITE_TIMEOUT)
|
|
242
245
|
accessory.logDebug('command sent successfully')
|
|
243
|
-
await sleep(
|
|
246
|
+
await sleep(SETTLE_DELAY)
|
|
244
247
|
} catch (err) {
|
|
245
248
|
accessory.logWarn(`BLE update failed: ${err.message}`)
|
|
246
249
|
throw err
|
package/lib/platform.js
CHANGED
|
@@ -13,6 +13,7 @@ import lanClient from './connection/lan.js'
|
|
|
13
13
|
import openApiClient from './connection/openapi.js'
|
|
14
14
|
import deviceTypes from './device/index.js'
|
|
15
15
|
import eveService from './fakegato/fakegato-history.js'
|
|
16
|
+
import { BLE_UPDATE_TIMEOUT } from './utils/ble-protocol.js'
|
|
16
17
|
import { buildCommand } from './utils/command-builder.js'
|
|
17
18
|
import platformConsts from './utils/constants.js'
|
|
18
19
|
import platformChars from './utils/custom-chars.js'
|
|
@@ -595,7 +596,10 @@ export default class {
|
|
|
595
596
|
concurrency: 1,
|
|
596
597
|
interval: this.config.bleControlInterval * 1000,
|
|
597
598
|
intervalCap: 1,
|
|
598
|
-
|
|
599
|
+
// Sized from the BLE stage timeouts so the two cannot drift apart. This
|
|
600
|
+
// is a backstop for a hung task, and must stay longer than the stages
|
|
601
|
+
// it wraps - see BLE_UPDATE_TIMEOUT.
|
|
602
|
+
timeout: BLE_UPDATE_TIMEOUT,
|
|
599
603
|
throwOnTimeout: true,
|
|
600
604
|
})
|
|
601
605
|
|
|
@@ -61,3 +61,35 @@ export const UUID = {
|
|
|
61
61
|
|
|
62
62
|
// Total BLE packet size
|
|
63
63
|
export const PACKET_SIZE = 20
|
|
64
|
+
|
|
65
|
+
// Timeouts for the individual stages of a single BLE updateDevice() call.
|
|
66
|
+
// They live here, rather than beside the code that uses them, so the platform
|
|
67
|
+
// can size its queue from them without importing the BLE client — that client
|
|
68
|
+
// pulls in a native module which is not present on every install.
|
|
69
|
+
export const BLE_STAGE_TIMEOUTS = {
|
|
70
|
+
powerOn: 5000,
|
|
71
|
+
connect: 10000,
|
|
72
|
+
write: 5000,
|
|
73
|
+
settle: 100, // let the controller transmit before disconnecting
|
|
74
|
+
|
|
75
|
+
// Discovering services, and the disconnect on the way out, have no timeout of
|
|
76
|
+
// their own, so leave them room in the total.
|
|
77
|
+
untimedStages: 3000,
|
|
78
|
+
|
|
79
|
+
// Headroom on top, so that a stage which times out reports its own error
|
|
80
|
+
// instead of being cut short by the queue's much vaguer one.
|
|
81
|
+
queueHeadroom: 2000,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The longest a single BLE updateDevice() call can legitimately take.
|
|
86
|
+
*
|
|
87
|
+
* The platform queue sizes its own timeout from this. It is meant as a backstop
|
|
88
|
+
* for a task that has hung, not a second deadline racing the stage timeouts.
|
|
89
|
+
* Both used to be a flat 10 seconds, which meant the queue killed a connection
|
|
90
|
+
* attempt at the exact moment that attempt gave up — and a light that connected
|
|
91
|
+
* slowly never got as far as sending its command at all (#1328).
|
|
92
|
+
*/
|
|
93
|
+
export const BLE_UPDATE_TIMEOUT = Object
|
|
94
|
+
.values(BLE_STAGE_TIMEOUTS)
|
|
95
|
+
.reduce((total, stage) => total + stage, 0)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { BLE_STAGE_TIMEOUTS, BLE_UPDATE_TIMEOUT } from './ble-protocol.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The bug these guard against (#1328): the BLE connect timeout and the platform
|
|
7
|
+
* queue timeout were both a flat 10000ms. The queue therefore killed a task at
|
|
8
|
+
* the exact moment the connect gave up, and any light that connected slowly had
|
|
9
|
+
* nothing left of the budget for discovering services and writing its command.
|
|
10
|
+
*/
|
|
11
|
+
describe('bLE timeout budget', () => {
|
|
12
|
+
it('leaves room for every stage of an update', () => {
|
|
13
|
+
const stages = BLE_STAGE_TIMEOUTS.powerOn
|
|
14
|
+
+ BLE_STAGE_TIMEOUTS.connect
|
|
15
|
+
+ BLE_STAGE_TIMEOUTS.untimedStages
|
|
16
|
+
+ BLE_STAGE_TIMEOUTS.write
|
|
17
|
+
+ BLE_STAGE_TIMEOUTS.settle
|
|
18
|
+
|
|
19
|
+
expect(BLE_UPDATE_TIMEOUT).toBeGreaterThan(stages)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('is not simply the connect timeout, which is what caused #1328', () => {
|
|
23
|
+
expect(BLE_UPDATE_TIMEOUT).not.toBe(BLE_STAGE_TIMEOUTS.connect)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('still has time left after the slowest possible connect', () => {
|
|
27
|
+
// A connect that only just succeeds must leave enough behind to discover
|
|
28
|
+
// services and send the command - otherwise a slow device is
|
|
29
|
+
// indistinguishable from an unreachable one.
|
|
30
|
+
const afterSlowestConnect = BLE_UPDATE_TIMEOUT
|
|
31
|
+
- BLE_STAGE_TIMEOUTS.powerOn
|
|
32
|
+
- BLE_STAGE_TIMEOUTS.connect
|
|
33
|
+
|
|
34
|
+
expect(afterSlowestConnect).toBeGreaterThanOrEqual(
|
|
35
|
+
BLE_STAGE_TIMEOUTS.untimedStages + BLE_STAGE_TIMEOUTS.write + BLE_STAGE_TIMEOUTS.settle,
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('keeps every stage timeout positive', () => {
|
|
40
|
+
Object.entries(BLE_STAGE_TIMEOUTS).forEach(([stage, ms]) => {
|
|
41
|
+
expect(ms, stage).toBeGreaterThan(0)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
package/package.json
CHANGED