@homebridge-plugins/homebridge-govee 11.34.2-beta.6 → 11.34.2-beta.8
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 +4 -1
- package/lib/device/base.js +21 -0
- package/lib/device/kettle.js +88 -8
- package/lib/device/sensor-leak.js +1 -13
- package/lib/device/sensor-monitor.js +11 -0
- package/lib/device/sensor-thermo-switch.js +1 -13
- package/lib/device/sensor-thermo.js +1 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -19,7 +19,10 @@ 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
|
|
22
|
+
- fix(H5054): don't let a slow leak warning block other sensors (#1348)
|
|
23
|
+
- fix: mark sensor batteries low at the configured threshold
|
|
24
|
+
- feat: show whether the kettle is heating, keeping warm or idle (#1337)
|
|
25
|
+
- fix: give the H5106 a battery service, so its low battery setting does something
|
|
23
26
|
|
|
24
27
|
## v11.34.1 (2026-08-06)
|
|
25
28
|
|
package/lib/device/base.js
CHANGED
|
@@ -53,6 +53,27 @@ export default class GoveeDevice {
|
|
|
53
53
|
return !!existing
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
updateBattery(battery) {
|
|
57
|
+
if (!this.battService || !Number.isFinite(battery) || battery < 0 || battery > 100) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (battery !== this.cacheBatt) {
|
|
62
|
+
this.cacheBatt = battery
|
|
63
|
+
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
|
|
64
|
+
this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const lowBattery = battery <= this.lowBattThreshold ? 1 : 0
|
|
68
|
+
const currentLowBattery = Number(this.battService
|
|
69
|
+
.getCharacteristic(this.hapChar.StatusLowBattery)
|
|
70
|
+
.value)
|
|
71
|
+
|
|
72
|
+
if (lowBattery !== currentLowBattery) {
|
|
73
|
+
this.battService.updateCharacteristic(this.hapChar.StatusLowBattery, lowBattery)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
/**
|
|
57
78
|
* Give up on a control, put it back the way it was, and tell HomeKit.
|
|
58
79
|
*
|
package/lib/device/kettle.js
CHANGED
|
@@ -28,12 +28,16 @@ import GoveeDevice from './base.js'
|
|
|
28
28
|
/**
|
|
29
29
|
* The water temperature out of a status frame, in celsius.
|
|
30
30
|
*
|
|
31
|
-
* The frame carries it as two bytes of hundredths of a degree fahrenheit.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
31
|
+
* The frame carries it as two bytes of hundredths of a degree fahrenheit. An
|
|
32
|
+
* owner confirmed that layout against three known points on an H7171 (#1337):
|
|
33
|
+
* cold water read 0x1edc = 79.00F, the moment the kettle beeped at its 142F
|
|
34
|
+
* target it read 0x3778 = 142.00F, and it drifted to 0x39d0 = 148.00F after.
|
|
35
|
+
*
|
|
36
|
+
* The result is still checked before it is used, because only the one model has
|
|
37
|
+
* been seen: a kettle holds water, and a figure outside freezing to a little
|
|
38
|
+
* past boiling is not a temperature at all - it means that model packs the
|
|
39
|
+
* frame differently. Returning nothing then leaves the tile absent rather than
|
|
40
|
+
* wrong.
|
|
37
41
|
*
|
|
38
42
|
* @param {string[]} hexParts the frame split into bytes
|
|
39
43
|
* @returns {number|undefined} the reading in celsius, or undefined if the
|
|
@@ -51,6 +55,33 @@ function readingToCelsius(hexParts) {
|
|
|
51
55
|
return farToCen(fahrenheit)
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* What the kettle says it is doing, from the state byte of an `aa 19` frame.
|
|
60
|
+
*
|
|
61
|
+
* Worked out from a full run an owner instrumented for us (#1337), matching the
|
|
62
|
+
* frames against a running commentary of what the kettle was doing:
|
|
63
|
+
*
|
|
64
|
+
* 00 idle two seconds after they switched it off
|
|
65
|
+
* 01 heating the moment they turned it on, and a second after setting a
|
|
66
|
+
* temperature on an earlier run
|
|
67
|
+
* 04 reached as the water hit the target and the kettle beeped
|
|
68
|
+
* 02 warming held for the next three and a half minutes of keep warm
|
|
69
|
+
*
|
|
70
|
+
* `04` and `02` arrive in the same second, so which of the pair marks reaching
|
|
71
|
+
* the target and which marks entering keep warm is read from their order, not
|
|
72
|
+
* proven separately. Both leave the kettle no longer heating, which is what the
|
|
73
|
+
* sensor reflects, so the distinction only affects the wording in the log.
|
|
74
|
+
*
|
|
75
|
+
* Only these four have been seen, all with keep warm switched on. Anything else
|
|
76
|
+
* falls through to the unknown-data report, which is how these were found.
|
|
77
|
+
*/
|
|
78
|
+
const HEATING_STATES = {
|
|
79
|
+
'00': { label: 'idle', heating: false },
|
|
80
|
+
'01': { label: 'heating', heating: true },
|
|
81
|
+
'02': { label: 'keeping warm', heating: false },
|
|
82
|
+
'04': { label: 'reached target', heating: false },
|
|
83
|
+
}
|
|
84
|
+
|
|
54
85
|
export default class extends GoveeDevice {
|
|
55
86
|
constructor(platform, accessory) {
|
|
56
87
|
super(platform, accessory)
|
|
@@ -294,6 +325,45 @@ export default class extends GoveeDevice {
|
|
|
294
325
|
}
|
|
295
326
|
}
|
|
296
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Reflect what the kettle says it is doing.
|
|
330
|
+
*
|
|
331
|
+
* The tile is added the first time a state frame arrives rather than up
|
|
332
|
+
* front, for the same reason as the temperature tile above: only the H7171
|
|
333
|
+
* has been seen sending these, and a tile that never updates cannot be told
|
|
334
|
+
* apart from a real reading.
|
|
335
|
+
*
|
|
336
|
+
* @param {string} stateByte the state byte of an `aa 19` frame
|
|
337
|
+
*/
|
|
338
|
+
updateHeatingState(stateByte) {
|
|
339
|
+
const state = HEATING_STATES[stateByte]
|
|
340
|
+
if (!state) {
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!this.serviceHeating) {
|
|
345
|
+
this.serviceHeating = this.accessory.getService('Heating')
|
|
346
|
+
if (!this.serviceHeating) {
|
|
347
|
+
this.serviceHeating = this.accessory.addService(this.hapServ.ContactSensor, 'Heating', 'heating')
|
|
348
|
+
this.serviceHeating.addCharacteristic(this.hapChar.ConfiguredName)
|
|
349
|
+
this.serviceHeating.updateCharacteristic(this.hapChar.ConfiguredName, 'Heating')
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (this.cacheHeatingState === state.label) {
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
this.cacheHeatingState = state.label
|
|
357
|
+
|
|
358
|
+
this.serviceHeating.updateCharacteristic(
|
|
359
|
+
this.hapChar.ContactSensorState,
|
|
360
|
+
state.heating
|
|
361
|
+
? this.hapChar.ContactSensorState.CONTACT_DETECTED
|
|
362
|
+
: this.hapChar.ContactSensorState.CONTACT_NOT_DETECTED,
|
|
363
|
+
)
|
|
364
|
+
this.accessory.log(`current status [${state.label}]`)
|
|
365
|
+
}
|
|
366
|
+
|
|
297
367
|
externalUpdate(params) {
|
|
298
368
|
// Handle OpenAPI workMode
|
|
299
369
|
if (params.workMode) {
|
|
@@ -369,13 +439,23 @@ export default class extends GoveeDevice {
|
|
|
369
439
|
case '2301': { // scheduled start on
|
|
370
440
|
break
|
|
371
441
|
}
|
|
442
|
+
// What the kettle is doing - see HEATING_STATES above. `1900` used to be
|
|
443
|
+
// treated as a startup frame, which it only looked like because a kettle
|
|
444
|
+
// is usually idle when it connects; it is really the idle state and is
|
|
445
|
+
// sent whenever the kettle reaches it.
|
|
446
|
+
case '1900':
|
|
447
|
+
case '1901':
|
|
448
|
+
case '1902':
|
|
449
|
+
case '1904': {
|
|
450
|
+
this.updateHeatingState(getTwoItemPosition(hexParts, 3))
|
|
451
|
+
break
|
|
452
|
+
}
|
|
372
453
|
// Also sent on startup, alongside the preset list above. What each one
|
|
373
454
|
// carries has not been worked out, and nothing here needs them, so they
|
|
374
455
|
// are known-and-ignored rather than reported every time (#1337)
|
|
375
456
|
case '1701': // a second form of the on-base report
|
|
376
457
|
case '1f06':
|
|
377
|
-
case '1f08':
|
|
378
|
-
case '1900': {
|
|
458
|
+
case '1f08': {
|
|
379
459
|
this.accessory.logDebug(`startup status [${deviceFunction}]: ${hexString}`)
|
|
380
460
|
break
|
|
381
461
|
}
|
|
@@ -47,19 +47,7 @@ export default class extends GoveeDevice {
|
|
|
47
47
|
this.platform.updateAccessoryStatus(this.accessory, this.cacheOnline)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
if (Number.isFinite(params.battery) && params.battery !== this.cacheBatt) {
|
|
52
|
-
// Battery is different so update Homebridge with new values
|
|
53
|
-
this.cacheBatt = params.battery
|
|
54
|
-
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
|
|
55
|
-
this.battService.updateCharacteristic(
|
|
56
|
-
this.hapChar.StatusLowBattery,
|
|
57
|
-
this.cacheBatt < this.lowBattThreshold ? 1 : 0,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
// Log the change
|
|
61
|
-
this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
|
|
62
|
-
}
|
|
50
|
+
this.updateBattery(params.battery)
|
|
63
51
|
|
|
64
52
|
// Check to see if the provided leak status is different from the cached state
|
|
65
53
|
if (hasProperty(params, 'leakDetected') && params.leakDetected !== this.cacheLeak) {
|
|
@@ -28,6 +28,15 @@ export default class extends GoveeDevice {
|
|
|
28
28
|
this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
29
29
|
this.updateCache()
|
|
30
30
|
|
|
31
|
+
// Add the battery service if it doesn't already exist. The device reports a
|
|
32
|
+
// battery in the HTTP device list the same way the thermo sensors do, and
|
|
33
|
+
// this handler already took a lowBattThreshold from the config - it just had
|
|
34
|
+
// nowhere to put the reading, so the setting did nothing and the level never
|
|
35
|
+
// reached HomeKit
|
|
36
|
+
this.battService = this.accessory.getService(this.hapServ.Battery)
|
|
37
|
+
|| this.accessory.addService(this.hapServ.Battery)
|
|
38
|
+
this.cacheBatt = this.battService.getCharacteristic(this.hapChar.BatteryLevel).value
|
|
39
|
+
|
|
31
40
|
// Add the humidity service if it doesn't already exist
|
|
32
41
|
this.humiService = this.accessory.getService(this.hapServ.HumiditySensor)
|
|
33
42
|
|| this.accessory.addService(this.hapServ.HumiditySensor)
|
|
@@ -59,6 +68,8 @@ export default class extends GoveeDevice {
|
|
|
59
68
|
// where the AWS channel never delivers for this model. The fields use the
|
|
60
69
|
// same semantics as the thermo-hygrometer sensors: hundredths of a degree
|
|
61
70
|
// and hundredths of a percent
|
|
71
|
+
this.updateBattery(params.battery)
|
|
72
|
+
|
|
62
73
|
if (hasProperty(params, 'temperature')) {
|
|
63
74
|
let newTemp = Number.parseInt(params.temperature + this.accessory.context.offTemp, 10)
|
|
64
75
|
newTemp /= 100
|
|
@@ -104,19 +104,7 @@ export default class extends GoveeDevice {
|
|
|
104
104
|
return
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
if (Number.isFinite(params.battery) && params.battery !== this.cacheBatt && this.battService) {
|
|
109
|
-
// Battery is different so update Homebridge with new values
|
|
110
|
-
this.cacheBatt = params.battery
|
|
111
|
-
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
|
|
112
|
-
this.battService.updateCharacteristic(
|
|
113
|
-
this.hapChar.StatusLowBattery,
|
|
114
|
-
this.cacheBatt < this.lowBattThreshold ? 1 : 0,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
// Log the change
|
|
118
|
-
this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
|
|
119
|
-
}
|
|
107
|
+
this.updateBattery(params.battery)
|
|
120
108
|
|
|
121
109
|
// Check to see if the provided temperature is different from the cached state
|
|
122
110
|
if (hasProperty(params, 'temperature')) {
|
|
@@ -80,19 +80,7 @@ export default class extends GoveeDevice {
|
|
|
80
80
|
return
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
if (Number.isFinite(params.battery) && params.battery !== this.cacheBatt && this.battService) {
|
|
85
|
-
// Battery is different so update Homebridge with new values
|
|
86
|
-
this.cacheBatt = params.battery
|
|
87
|
-
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
|
|
88
|
-
this.battService.updateCharacteristic(
|
|
89
|
-
this.hapChar.StatusLowBattery,
|
|
90
|
-
this.cacheBatt < this.lowBattThreshold ? 1 : 0,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
// Log the change
|
|
94
|
-
this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
|
|
95
|
-
}
|
|
83
|
+
this.updateBattery(params.battery)
|
|
96
84
|
|
|
97
85
|
// Check to see if the provided temperature is different from the cached state
|
|
98
86
|
if (hasProperty(params, 'temperature')) {
|
package/package.json
CHANGED