@homebridge-plugins/homebridge-govee 11.34.2-beta.1 → 11.34.2-beta.11
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 +16 -0
- package/config.schema.json +10 -5
- package/lib/connection/lan.js +17 -2
- package/lib/device/base.js +21 -0
- package/lib/device/fan-H7105.js +23 -7
- package/lib/device/fan-H7107.js +23 -7
- package/lib/device/heater2.js +23 -7
- package/lib/device/humidifier-H7142.js +23 -7
- package/lib/device/humidifier-H7160.js +23 -7
- package/lib/device/kettle.js +88 -8
- package/lib/device/light.js +483 -38
- package/lib/device/sensor-leak.js +1 -13
- package/lib/device/sensor-monitor.js +12 -1
- package/lib/device/sensor-thermo-switch.js +1 -13
- package/lib/device/sensor-thermo.js +1 -13
- package/lib/platform.js +133 -110
- package/lib/utils/command-builder.js +121 -0
- package/lib/utils/config-parser.js +9 -1
- package/lib/utils/constants.js +34 -1
- package/lib/utils/device-capabilities.js +6 -0
- package/lib/utils/device-context.js +22 -2
- package/lib/utils/dispatch.js +13 -2
- package/lib/utils/lang-en.js +4 -2
- package/lib/utils/validation.js +17 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,22 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
|
|
|
9
9
|
- feat: control the H1250 zones with the device's own frame, as govee ignores its toggles (#1343) (@Principe1218)
|
|
10
10
|
- fix: explain in the log when zone tiles are missing because aws credentials are not set
|
|
11
11
|
- fix: restore debug logging when the plugin runs in a child bridge
|
|
12
|
+
- fix: turn adaptive lighting off when a colour is chosen outside HomeKit
|
|
13
|
+
- fix: send a colour change when only the saturation moves, on every night light
|
|
14
|
+
- fix: listen for saturation on the main light too, not only hue
|
|
15
|
+
- fix: accept 20-byte H5106 status frames (#1345) (@danieloleary)
|
|
16
|
+
- fix: stop rescaling the H1250's brightness, since it already counts to 100 over AWS (#1347) (@Principe1218)
|
|
17
|
+
- feat: give the H1250's zones independent color and brightness (#1346) (@Principe1218)
|
|
18
|
+
- fix: stop a lan scan that cannot reach the network from crashing the bridge
|
|
19
|
+
- fix: apply the openapi disable and refresh settings instead of asking for them to be removed
|
|
20
|
+
- fix: say once when a bluetooth reading arrives for an unknown device, not on every broadcast
|
|
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) (@danieloleary)
|
|
23
|
+
- fix: mark sensor batteries low at the configured threshold (#1349) (@danieloleary)
|
|
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
|
|
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)
|
|
12
28
|
|
|
13
29
|
## v11.34.1 (2026-08-06)
|
|
14
30
|
|
package/config.schema.json
CHANGED
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"type": "integer",
|
|
50
50
|
"description": "An interval (in seconds) in which your HTTP devices will refresh with the plugin. Must be 30 or more.",
|
|
51
51
|
"placeholder": 30,
|
|
52
|
-
"minimum": 30
|
|
52
|
+
"minimum": 30,
|
|
53
|
+
"maximum": 2073600
|
|
53
54
|
},
|
|
54
55
|
"awsDisable": {
|
|
55
56
|
"title": "Disable AWS",
|
|
@@ -69,7 +70,8 @@
|
|
|
69
70
|
"minimum": 60,
|
|
70
71
|
"condition": {
|
|
71
72
|
"functionBody": "return (!model.bleDisable);"
|
|
72
|
-
}
|
|
73
|
+
},
|
|
74
|
+
"maximum": 2073600
|
|
73
75
|
},
|
|
74
76
|
"lanDisable": {
|
|
75
77
|
"title": "Disable LAN",
|
|
@@ -84,7 +86,8 @@
|
|
|
84
86
|
"minimum": 10,
|
|
85
87
|
"condition": {
|
|
86
88
|
"functionBody": "return (!model.lanDisable);"
|
|
87
|
-
}
|
|
89
|
+
},
|
|
90
|
+
"maximum": 2073600
|
|
88
91
|
},
|
|
89
92
|
"lanScanInterval": {
|
|
90
93
|
"title": "LAN Scan Interval",
|
|
@@ -94,7 +97,8 @@
|
|
|
94
97
|
"minimum": 30,
|
|
95
98
|
"condition": {
|
|
96
99
|
"functionBody": "return (!model.lanDisable);"
|
|
97
|
-
}
|
|
100
|
+
},
|
|
101
|
+
"maximum": 2073600
|
|
98
102
|
},
|
|
99
103
|
"lanScanSubnets": {
|
|
100
104
|
"title": "LAN Scan Subnets",
|
|
@@ -118,7 +122,8 @@
|
|
|
118
122
|
"minimum": 30,
|
|
119
123
|
"condition": {
|
|
120
124
|
"functionBody": "return (!model.openApiDisable);"
|
|
121
|
-
}
|
|
125
|
+
},
|
|
126
|
+
"maximum": 2073600
|
|
122
127
|
},
|
|
123
128
|
"bleControlInterval": {
|
|
124
129
|
"title": "BLE Control Interval",
|
package/lib/connection/lan.js
CHANGED
|
@@ -181,6 +181,12 @@ export default class {
|
|
|
181
181
|
reject(err)
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
+
// Handle errors on the sending socket. Without a listener here, node throws
|
|
185
|
+
// the error object instead of emitting it, which takes the bridge down.
|
|
186
|
+
this.sender.on('error', (err) => {
|
|
187
|
+
this.log.warn('[LAN] client error: %s.', parseError(err))
|
|
188
|
+
})
|
|
189
|
+
|
|
184
190
|
// Handle started listening for messages
|
|
185
191
|
this.receiver.on('listening', () => {
|
|
186
192
|
const { address, port } = this.receiver.address()
|
|
@@ -233,16 +239,25 @@ export default class {
|
|
|
233
239
|
sendScanCommand() {
|
|
234
240
|
const scanCommand = JSON.stringify({ msg: { cmd: commands.scan, data: { account_topic: 'reserve' } } })
|
|
235
241
|
this.log.debug('[LAN] scanning for devices over LAN...')
|
|
236
|
-
this.sender.send(scanCommand, scanCommandPort, multicastIp)
|
|
242
|
+
this.sender.send(scanCommand, scanCommandPort, multicastIp, err => this.logScanSendError(multicastIp, err))
|
|
237
243
|
|
|
238
244
|
// Also scan any configured subnets directly. Multicast doesn't cross
|
|
239
245
|
// between subnets, so devices on a different subnet than Homebridge are
|
|
240
246
|
// reached by sending the scan straight to each host (they reply on 4002).
|
|
241
247
|
this.scanHosts.forEach((host) => {
|
|
242
|
-
this.sender.send(scanCommand, scanCommandPort, host)
|
|
248
|
+
this.sender.send(scanCommand, scanCommandPort, host, err => this.logScanSendError(host, err))
|
|
243
249
|
})
|
|
244
250
|
}
|
|
245
251
|
|
|
252
|
+
// A scan send can fail on a host with no route to the target, for example when
|
|
253
|
+
// multicast is unavailable. Report it and carry on rather than letting dgram
|
|
254
|
+
// emit an unhandled 'error' event, which would crash the bridge every scan.
|
|
255
|
+
logScanSendError(target, err) {
|
|
256
|
+
if (err) {
|
|
257
|
+
this.log.debug('[LAN] scan request to %s failed [%s].', target, parseError(err))
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
246
261
|
// Get all available LAN devices
|
|
247
262
|
async getDevices() {
|
|
248
263
|
return new Promise((resolve) => {
|
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/fan-H7105.js
CHANGED
|
@@ -111,6 +111,14 @@ export default class extends GoveeDevice {
|
|
|
111
111
|
this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
112
112
|
await this.internalColourUpdate(value)
|
|
113
113
|
})
|
|
114
|
+
// Saturation needs a handler of its own. HomeKit writes hue and saturation
|
|
115
|
+
// separately, so with only hue listened for, dragging saturation alone -
|
|
116
|
+
// the same colour made paler or richer - reached nothing and the light
|
|
117
|
+
// never changed. Both handlers share one update, and the debounce inside
|
|
118
|
+
// it collapses a hue-and-saturation pair into a single command.
|
|
119
|
+
this.lightService.getCharacteristic(this.hapChar.Saturation).onSet(async () => {
|
|
120
|
+
await this.internalColourUpdate(this.lightService.getCharacteristic(this.hapChar.Hue).value)
|
|
121
|
+
})
|
|
114
122
|
this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
|
|
115
123
|
this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
116
124
|
}
|
|
@@ -319,13 +327,16 @@ export default class extends GoveeDevice {
|
|
|
319
327
|
return
|
|
320
328
|
}
|
|
321
329
|
|
|
322
|
-
// Don't continue if the new
|
|
323
|
-
|
|
330
|
+
// Don't continue if the new colour is the same as before. Saturation is
|
|
331
|
+
// part of that question: comparing hue alone meant a change of saturation
|
|
332
|
+
// at the same hue looked like no change at all, and nothing was sent.
|
|
333
|
+
const currentSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
334
|
+
if (value === this.cacheHue && currentSat === this.cacheSat) {
|
|
324
335
|
return
|
|
325
336
|
}
|
|
326
337
|
|
|
327
338
|
// Calculate RGB values
|
|
328
|
-
const newRGB = hs2rgb(value,
|
|
339
|
+
const newRGB = hs2rgb(value, currentSat)
|
|
329
340
|
|
|
330
341
|
// Generate the hex values for the code
|
|
331
342
|
const hexValues = [0x3A, 0x1B, 0x05, 0x0D, ...newRGB]
|
|
@@ -336,7 +347,10 @@ export default class extends GoveeDevice {
|
|
|
336
347
|
value: generateCodeFromHexValues(hexValues),
|
|
337
348
|
})
|
|
338
349
|
|
|
339
|
-
// Cache the new state and log if appropriate
|
|
350
|
+
// Cache the new state and log if appropriate. Saturation is cached on
|
|
351
|
+
// every send, not only when the hue moved, or a change of saturation at
|
|
352
|
+
// the same hue would be sent once and then look unchanged ever after.
|
|
353
|
+
this.cacheSat = currentSat
|
|
340
354
|
if (this.cacheHue !== value) {
|
|
341
355
|
this.cacheHue = value
|
|
342
356
|
this.accessory.log(`${platformLang.curColour} [rgb ${newRGB.join(' ')}]`)
|
|
@@ -450,12 +464,14 @@ export default class extends GoveeDevice {
|
|
|
450
464
|
|
|
451
465
|
const hs = rgb2hs(newR, newG, newB)
|
|
452
466
|
|
|
453
|
-
// Check for a colour change
|
|
454
|
-
|
|
467
|
+
// Check for a colour change. Saturation counts as much as hue, or a
|
|
468
|
+
// change of saturation made in the Govee app is reported back and
|
|
469
|
+
// ignored, leaving the tile showing the old colour.
|
|
470
|
+
if (hs[0] !== this.cacheHue || hs[1] !== this.cacheSat) {
|
|
455
471
|
// Colour is different so update Homebridge with new values
|
|
456
472
|
this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
457
473
|
this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
|
|
458
|
-
[this.cacheHue] = hs
|
|
474
|
+
[this.cacheHue, this.cacheSat] = hs
|
|
459
475
|
|
|
460
476
|
// Log the change
|
|
461
477
|
this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
|
package/lib/device/fan-H7107.js
CHANGED
|
@@ -111,6 +111,14 @@ export default class extends GoveeDevice {
|
|
|
111
111
|
this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
112
112
|
await this.internalColourUpdate(value)
|
|
113
113
|
})
|
|
114
|
+
// Saturation needs a handler of its own. HomeKit writes hue and saturation
|
|
115
|
+
// separately, so with only hue listened for, dragging saturation alone -
|
|
116
|
+
// the same colour made paler or richer - reached nothing and the light
|
|
117
|
+
// never changed. Both handlers share one update, and the debounce inside
|
|
118
|
+
// it collapses a hue-and-saturation pair into a single command.
|
|
119
|
+
this.lightService.getCharacteristic(this.hapChar.Saturation).onSet(async () => {
|
|
120
|
+
await this.internalColourUpdate(this.lightService.getCharacteristic(this.hapChar.Hue).value)
|
|
121
|
+
})
|
|
114
122
|
this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
|
|
115
123
|
this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
116
124
|
}
|
|
@@ -323,13 +331,16 @@ export default class extends GoveeDevice {
|
|
|
323
331
|
return
|
|
324
332
|
}
|
|
325
333
|
|
|
326
|
-
// Don't continue if the new
|
|
327
|
-
|
|
334
|
+
// Don't continue if the new colour is the same as before. Saturation is
|
|
335
|
+
// part of that question: comparing hue alone meant a change of saturation
|
|
336
|
+
// at the same hue looked like no change at all, and nothing was sent.
|
|
337
|
+
const currentSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
338
|
+
if (value === this.cacheHue && currentSat === this.cacheSat) {
|
|
328
339
|
return
|
|
329
340
|
}
|
|
330
341
|
|
|
331
342
|
// Calculate RGB values
|
|
332
|
-
const newRGB = hs2rgb(value,
|
|
343
|
+
const newRGB = hs2rgb(value, currentSat)
|
|
333
344
|
|
|
334
345
|
// Generate the hex values for the code
|
|
335
346
|
const hexValues = [0x3A, 0x1B, 0x05, 0x0D, ...newRGB]
|
|
@@ -340,7 +351,10 @@ export default class extends GoveeDevice {
|
|
|
340
351
|
value: generateCodeFromHexValues(hexValues),
|
|
341
352
|
})
|
|
342
353
|
|
|
343
|
-
// Cache the new state and log if appropriate
|
|
354
|
+
// Cache the new state and log if appropriate. Saturation is cached on
|
|
355
|
+
// every send, not only when the hue moved, or a change of saturation at
|
|
356
|
+
// the same hue would be sent once and then look unchanged ever after.
|
|
357
|
+
this.cacheSat = currentSat
|
|
344
358
|
if (this.cacheHue !== value) {
|
|
345
359
|
this.cacheHue = value
|
|
346
360
|
this.accessory.log(`${platformLang.curColour} [rgb ${newRGB.join(' ')}]`)
|
|
@@ -454,12 +468,14 @@ export default class extends GoveeDevice {
|
|
|
454
468
|
|
|
455
469
|
const hs = rgb2hs(newR, newG, newB)
|
|
456
470
|
|
|
457
|
-
// Check for a colour change
|
|
458
|
-
|
|
471
|
+
// Check for a colour change. Saturation counts as much as hue, or a
|
|
472
|
+
// change of saturation made in the Govee app is reported back and
|
|
473
|
+
// ignored, leaving the tile showing the old colour.
|
|
474
|
+
if (hs[0] !== this.cacheHue || hs[1] !== this.cacheSat) {
|
|
459
475
|
// Colour is different so update Homebridge with new values
|
|
460
476
|
this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
461
477
|
this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
|
|
462
|
-
[this.cacheHue] = hs
|
|
478
|
+
[this.cacheHue, this.cacheSat] = hs
|
|
463
479
|
|
|
464
480
|
// Log the change
|
|
465
481
|
this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
|
package/lib/device/heater2.js
CHANGED
|
@@ -221,6 +221,14 @@ export default class extends GoveeDevice {
|
|
|
221
221
|
this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
222
222
|
await this.internalColourUpdate(value)
|
|
223
223
|
})
|
|
224
|
+
// Saturation needs a handler of its own. HomeKit writes hue and saturation
|
|
225
|
+
// separately, so with only hue listened for, dragging saturation alone -
|
|
226
|
+
// the same colour made paler or richer - reached nothing and the light
|
|
227
|
+
// never changed. Both handlers share one update, and the debounce inside
|
|
228
|
+
// it collapses a hue-and-saturation pair into a single command.
|
|
229
|
+
this.lightService.getCharacteristic(this.hapChar.Saturation).onSet(async () => {
|
|
230
|
+
await this.internalColourUpdate(this.lightService.getCharacteristic(this.hapChar.Hue).value)
|
|
231
|
+
})
|
|
224
232
|
this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
|
|
225
233
|
this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
226
234
|
|
|
@@ -574,13 +582,16 @@ export default class extends GoveeDevice {
|
|
|
574
582
|
return
|
|
575
583
|
}
|
|
576
584
|
|
|
577
|
-
// Don't continue if the new
|
|
578
|
-
|
|
585
|
+
// Don't continue if the new colour is the same as before. Saturation is
|
|
586
|
+
// part of that question: comparing hue alone meant a change of saturation
|
|
587
|
+
// at the same hue looked like no change at all, and nothing was sent.
|
|
588
|
+
const currentSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
589
|
+
if (value === this.cacheHue && currentSat === this.cacheSat) {
|
|
579
590
|
return
|
|
580
591
|
}
|
|
581
592
|
|
|
582
593
|
// Calculate RGB values
|
|
583
|
-
const newRGB = hs2rgb(value,
|
|
594
|
+
const newRGB = hs2rgb(value, currentSat)
|
|
584
595
|
|
|
585
596
|
// Generate the hex values for the code
|
|
586
597
|
const hexValues = [0x3A, 0x1B, 0x05, 0x0D, ...newRGB]
|
|
@@ -591,7 +602,10 @@ export default class extends GoveeDevice {
|
|
|
591
602
|
value: generateCodeFromHexValues(hexValues),
|
|
592
603
|
})
|
|
593
604
|
|
|
594
|
-
// Cache the new state and log if appropriate
|
|
605
|
+
// Cache the new state and log if appropriate. Saturation is cached on
|
|
606
|
+
// every send, not only when the hue moved, or a change of saturation at
|
|
607
|
+
// the same hue would be sent once and then look unchanged ever after.
|
|
608
|
+
this.cacheSat = currentSat
|
|
595
609
|
if (this.cacheHue !== value) {
|
|
596
610
|
this.cacheHue = value
|
|
597
611
|
this.accessory.log(`${platformLang.curColour} [rgb ${newRGB.join(' ')}]`)
|
|
@@ -731,12 +745,14 @@ export default class extends GoveeDevice {
|
|
|
731
745
|
|
|
732
746
|
const hs = rgb2hs(newR, newG, newB)
|
|
733
747
|
|
|
734
|
-
// Check for a colour change
|
|
735
|
-
|
|
748
|
+
// Check for a colour change. Saturation counts as much as hue, or a
|
|
749
|
+
// change of saturation made in the Govee app is reported back and
|
|
750
|
+
// ignored, leaving the tile showing the old colour.
|
|
751
|
+
if (hs[0] !== this.cacheHue || hs[1] !== this.cacheSat) {
|
|
736
752
|
// Colour is different so update Homebridge with new values
|
|
737
753
|
this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
738
754
|
this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
|
|
739
|
-
[this.cacheHue] = hs
|
|
755
|
+
[this.cacheHue, this.cacheSat] = hs
|
|
740
756
|
|
|
741
757
|
// Log the change
|
|
742
758
|
this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
|
|
@@ -122,6 +122,14 @@ export default class extends GoveeDevice {
|
|
|
122
122
|
this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
123
123
|
await this.internalColourUpdate(value)
|
|
124
124
|
})
|
|
125
|
+
// Saturation needs a handler of its own. HomeKit writes hue and saturation
|
|
126
|
+
// separately, so with only hue listened for, dragging saturation alone -
|
|
127
|
+
// the same colour made paler or richer - reached nothing and the light
|
|
128
|
+
// never changed. Both handlers share one update, and the debounce inside
|
|
129
|
+
// it collapses a hue-and-saturation pair into a single command.
|
|
130
|
+
this.lightService.getCharacteristic(this.hapChar.Saturation).onSet(async () => {
|
|
131
|
+
await this.internalColourUpdate(this.lightService.getCharacteristic(this.hapChar.Hue).value)
|
|
132
|
+
})
|
|
125
133
|
this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
|
|
126
134
|
this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
127
135
|
|
|
@@ -318,15 +326,18 @@ export default class extends GoveeDevice {
|
|
|
318
326
|
return
|
|
319
327
|
}
|
|
320
328
|
|
|
321
|
-
// Don't continue if the new
|
|
322
|
-
|
|
329
|
+
// Don't continue if the new colour is the same as before. Saturation is
|
|
330
|
+
// part of that question: comparing hue alone meant a change of saturation
|
|
331
|
+
// at the same hue looked like no change at all, and nothing was sent.
|
|
332
|
+
const currentSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
333
|
+
if (value === this.cacheHue && currentSat === this.cacheSat) {
|
|
323
334
|
return
|
|
324
335
|
}
|
|
325
336
|
|
|
326
337
|
// Generate the hex values for the code
|
|
327
338
|
const newRGB = hs2rgb(
|
|
328
339
|
value,
|
|
329
|
-
|
|
340
|
+
currentSat,
|
|
330
341
|
)
|
|
331
342
|
const hexValues = [0x33, 0x1B, 0x01, this.cacheBright, ...newRGB]
|
|
332
343
|
|
|
@@ -336,7 +347,10 @@ export default class extends GoveeDevice {
|
|
|
336
347
|
value: generateCodeFromHexValues(hexValues),
|
|
337
348
|
})
|
|
338
349
|
|
|
339
|
-
// Cache the new state and log if appropriate
|
|
350
|
+
// Cache the new state and log if appropriate. Saturation is cached on
|
|
351
|
+
// every send, not only when the hue moved, or a change of saturation at
|
|
352
|
+
// the same hue would be sent once and then look unchanged ever after.
|
|
353
|
+
this.cacheSat = currentSat
|
|
340
354
|
if (this.cacheHue !== value) {
|
|
341
355
|
this.cacheHue = value
|
|
342
356
|
this.accessory.log(`${platformLang.curColour} [rgb ${newRGB.join(' ')}]`)
|
|
@@ -487,12 +501,14 @@ export default class extends GoveeDevice {
|
|
|
487
501
|
const newB = hexToDecimal(getTwoItemPosition(hexParts, 7))
|
|
488
502
|
const hs = rgb2hs(newR, newG, newB)
|
|
489
503
|
|
|
490
|
-
// Check for a colour change
|
|
491
|
-
|
|
504
|
+
// Check for a colour change. Saturation counts as much as hue, or a
|
|
505
|
+
// change of saturation made in the Govee app is reported back and
|
|
506
|
+
// ignored, leaving the tile showing the old colour.
|
|
507
|
+
if (hs[0] !== this.cacheHue || hs[1] !== this.cacheSat) {
|
|
492
508
|
// Colour is different so update Homebridge with new values
|
|
493
509
|
this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
494
510
|
this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
|
|
495
|
-
[this.cacheHue] = hs
|
|
511
|
+
[this.cacheHue, this.cacheSat] = hs
|
|
496
512
|
|
|
497
513
|
// Log the change
|
|
498
514
|
this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
|
|
@@ -85,6 +85,14 @@ export default class extends GoveeDevice {
|
|
|
85
85
|
this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
86
86
|
await this.internalColourUpdate(value)
|
|
87
87
|
})
|
|
88
|
+
// Saturation needs a handler of its own. HomeKit writes hue and saturation
|
|
89
|
+
// separately, so with only hue listened for, dragging saturation alone -
|
|
90
|
+
// the same colour made paler or richer - reached nothing and the light
|
|
91
|
+
// never changed. Both handlers share one update, and the debounce inside
|
|
92
|
+
// it collapses a hue-and-saturation pair into a single command.
|
|
93
|
+
this.lightService.getCharacteristic(this.hapChar.Saturation).onSet(async () => {
|
|
94
|
+
await this.internalColourUpdate(this.lightService.getCharacteristic(this.hapChar.Hue).value)
|
|
95
|
+
})
|
|
88
96
|
this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
|
|
89
97
|
this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
90
98
|
|
|
@@ -266,15 +274,18 @@ export default class extends GoveeDevice {
|
|
|
266
274
|
return
|
|
267
275
|
}
|
|
268
276
|
|
|
269
|
-
// Don't continue if the new
|
|
270
|
-
|
|
277
|
+
// Don't continue if the new colour is the same as before. Saturation is
|
|
278
|
+
// part of that question: comparing hue alone meant a change of saturation
|
|
279
|
+
// at the same hue looked like no change at all, and nothing was sent.
|
|
280
|
+
const currentSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
|
|
281
|
+
if (value === this.cacheHue && currentSat === this.cacheSat) {
|
|
271
282
|
return
|
|
272
283
|
}
|
|
273
284
|
|
|
274
285
|
// Generate the hex values for the code
|
|
275
286
|
const newRGB = hs2rgb(
|
|
276
287
|
value,
|
|
277
|
-
|
|
288
|
+
currentSat,
|
|
278
289
|
)
|
|
279
290
|
const hexValues = [0x33, 0x1B, 0x01, this.cacheBright, ...newRGB]
|
|
280
291
|
|
|
@@ -284,7 +295,10 @@ export default class extends GoveeDevice {
|
|
|
284
295
|
value: generateCodeFromHexValues(hexValues),
|
|
285
296
|
})
|
|
286
297
|
|
|
287
|
-
// Cache the new state and log if appropriate
|
|
298
|
+
// Cache the new state and log if appropriate. Saturation is cached on
|
|
299
|
+
// every send, not only when the hue moved, or a change of saturation at
|
|
300
|
+
// the same hue would be sent once and then look unchanged ever after.
|
|
301
|
+
this.cacheSat = currentSat
|
|
288
302
|
if (this.cacheHue !== value) {
|
|
289
303
|
this.cacheHue = value
|
|
290
304
|
this.accessory.log(`${platformLang.curColour} [rgb ${newRGB.join(' ')}]`)
|
|
@@ -411,12 +425,14 @@ export default class extends GoveeDevice {
|
|
|
411
425
|
const newB = hexToDecimal(getTwoItemPosition(hexParts, 7))
|
|
412
426
|
const hs = rgb2hs(newR, newG, newB)
|
|
413
427
|
|
|
414
|
-
// Check for a colour change
|
|
415
|
-
|
|
428
|
+
// Check for a colour change. Saturation counts as much as hue, or a
|
|
429
|
+
// change of saturation made in the Govee app is reported back and
|
|
430
|
+
// ignored, leaving the tile showing the old colour.
|
|
431
|
+
if (hs[0] !== this.cacheHue || hs[1] !== this.cacheSat) {
|
|
416
432
|
// Colour is different so update Homebridge with new values
|
|
417
433
|
this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
418
434
|
this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
|
|
419
|
-
[this.cacheHue] = hs
|
|
435
|
+
[this.cacheHue, this.cacheSat] = hs
|
|
420
436
|
|
|
421
437
|
// Log the change
|
|
422
438
|
this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
|
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
|
}
|