@homebridge-plugins/homebridge-govee 11.34.2-beta.1 → 11.34.2-beta.3
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 +8 -0
- package/config.schema.json +10 -5
- package/lib/connection/lan.js +17 -2
- 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/light.js +46 -23
- package/lib/device/sensor-monitor.js +1 -1
- package/lib/platform.js +16 -3
- package/lib/utils/config-parser.js +9 -1
- package/lib/utils/constants.js +16 -0
- package/lib/utils/lang-en.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,14 @@ 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 a lan scan that cannot reach the network from crashing the bridge
|
|
17
|
+
- fix: apply the openapi disable and refresh settings instead of asking for them to be removed
|
|
18
|
+
- fix: say once when a bluetooth reading arrives for an unknown device, not on every broadcast
|
|
19
|
+
- fix: reject a refresh time so large it would make the plugin poll every millisecond
|
|
12
20
|
|
|
13
21
|
## v11.34.1 (2026-08-06)
|
|
14
22
|
|
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/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/light.js
CHANGED
|
@@ -72,10 +72,22 @@ export default class extends GoveeDevice {
|
|
|
72
72
|
this.service = this.accessory.getService(this.hapServ.Lightbulb)
|
|
73
73
|
|| this.accessory.addService(this.hapServ.Lightbulb)
|
|
74
74
|
|
|
75
|
-
//
|
|
75
|
+
// Adaptive lighting has just been switched off, so take away the three
|
|
76
|
+
// characteristics that make Home offer it. Only these need to go: the whole
|
|
77
|
+
// Lightbulb service used to be thrown away and rebuilt here, which does hide
|
|
78
|
+
// the icon but presents Home with what looks like a different light, and an
|
|
79
|
+
// owner can lose that light's room and its automations as a result.
|
|
76
80
|
if ((this.colourSafeMode || this.alShift === -1) && this.accessory.context.adaptiveLighting) {
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
[
|
|
82
|
+
'SupportedCharacteristicValueTransitionConfiguration',
|
|
83
|
+
'CharacteristicValueTransitionControl',
|
|
84
|
+
'CharacteristicValueActiveTransitionCount',
|
|
85
|
+
].forEach((charName) => {
|
|
86
|
+
const char = this.hapChar[charName]
|
|
87
|
+
if (char && this.service.testCharacteristic(char)) {
|
|
88
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(char))
|
|
89
|
+
}
|
|
90
|
+
})
|
|
79
91
|
this.accessory.context.adaptiveLighting = false
|
|
80
92
|
}
|
|
81
93
|
|
|
@@ -200,6 +212,14 @@ export default class extends GoveeDevice {
|
|
|
200
212
|
this.service.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
201
213
|
await this.internalColourUpdate(value)
|
|
202
214
|
})
|
|
215
|
+
// Saturation needs a handler of its own. HomeKit writes hue and saturation
|
|
216
|
+
// separately, so with only hue listened for, dragging saturation alone -
|
|
217
|
+
// the same colour made paler or richer - reached nothing at all. Both
|
|
218
|
+
// handlers share one update, and the debounce inside it collapses a
|
|
219
|
+
// hue-and-saturation pair into a single command.
|
|
220
|
+
this.service.getCharacteristic(this.hapChar.Saturation).onSet(async () => {
|
|
221
|
+
await this.internalColourUpdate(this.service.getCharacteristic(this.hapChar.Hue).value)
|
|
222
|
+
})
|
|
203
223
|
this.cacheHue = this.service.getCharacteristic(this.hapChar.Hue).value
|
|
204
224
|
this.cacheSat = this.service.getCharacteristic(this.hapChar.Saturation).value
|
|
205
225
|
|
|
@@ -663,7 +683,7 @@ export default class extends GoveeDevice {
|
|
|
663
683
|
let rgb
|
|
664
684
|
let mired
|
|
665
685
|
let colourChange = false
|
|
666
|
-
let
|
|
686
|
+
let changedElsewhere = false
|
|
667
687
|
if (params.kelvin) {
|
|
668
688
|
mired = Math.round(1000000 / params.kelvin)
|
|
669
689
|
hs = m2hs(mired)
|
|
@@ -672,28 +692,28 @@ export default class extends GoveeDevice {
|
|
|
672
692
|
// Check for a colour change
|
|
673
693
|
if (params.kelvin !== this.cacheKelvin) {
|
|
674
694
|
colourChange = true
|
|
675
|
-
|
|
676
|
-
//
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
sigColourChange = true
|
|
680
|
-
}
|
|
695
|
+
// Commands go out rounded to the nearest 100K, so a report that lands
|
|
696
|
+
// within that is this light repeating back what it was just told.
|
|
697
|
+
// Anything further apart came from somewhere else - see below
|
|
698
|
+
changedElsewhere = Math.abs(params.kelvin - this.cacheKelvin) > 100
|
|
681
699
|
}
|
|
682
700
|
} else {
|
|
683
701
|
rgb = [params.rgb.r, params.rgb.g, params.rgb.b]
|
|
684
702
|
hs = rgb2hs(rgb[0], rgb[1], rgb[2])
|
|
685
703
|
|
|
686
|
-
// Check for a colour change
|
|
687
|
-
|
|
704
|
+
// Check for a colour change. Saturation matters as much as hue: going
|
|
705
|
+
// from a pale colour to a vivid one at the same hue is a real change,
|
|
706
|
+
// and comparing hue alone missed it entirely - so the light was left
|
|
707
|
+
// following adaptive lighting after its owner had chosen a colour, and
|
|
708
|
+
// wandered off that colour a few minutes later (#1333)
|
|
709
|
+
if (hs[0] !== this.cacheHue || hs[1] !== this.cacheSat) {
|
|
688
710
|
colourChange = true
|
|
689
|
-
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
sigColourChange = true
|
|
696
|
-
}
|
|
711
|
+
// A light repeating our own command back can drift by a unit or two
|
|
712
|
+
// through the colour conversions on the way out and back. Beyond that
|
|
713
|
+
// drift, somebody else set this colour
|
|
714
|
+
changedElsewhere = Math.abs(rgb[0] - this.cacheR) > 5
|
|
715
|
+
|| Math.abs(rgb[1] - this.cacheG) > 5
|
|
716
|
+
|| Math.abs(rgb[2] - this.cacheB) > 5
|
|
697
717
|
}
|
|
698
718
|
}
|
|
699
719
|
|
|
@@ -703,7 +723,7 @@ export default class extends GoveeDevice {
|
|
|
703
723
|
this.service.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
704
724
|
this.service.updateCharacteristic(this.hapChar.Saturation, hs[1]);
|
|
705
725
|
[this.cacheR, this.cacheG, this.cacheB] = rgb;
|
|
706
|
-
[this.cacheHue] = hs
|
|
726
|
+
[this.cacheHue, this.cacheSat] = hs
|
|
707
727
|
|
|
708
728
|
if (mired) {
|
|
709
729
|
if (!this.colourSafeMode) {
|
|
@@ -716,8 +736,11 @@ export default class extends GoveeDevice {
|
|
|
716
736
|
this.accessory.log(`${platformLang.curColour} [rgb ${this.cacheR} ${this.cacheG} ${this.cacheB}]`)
|
|
717
737
|
}
|
|
718
738
|
|
|
719
|
-
//
|
|
720
|
-
|
|
739
|
+
// Somebody chose this colour outside HomeKit - in the Govee app, on a
|
|
740
|
+
// remote, or at the wall. HomeKit's own colour changes are caught by
|
|
741
|
+
// Homebridge itself, but nothing else sees these, so adaptive lighting
|
|
742
|
+
// would carry on and move the light off that colour within minutes.
|
|
743
|
+
if (!this.colourSafeMode && this.alController?.isAdaptiveLightingActive?.() && changedElsewhere) {
|
|
721
744
|
this.alController.disableAdaptiveLighting()
|
|
722
745
|
this.accessory.log(platformLang.alDisabled)
|
|
723
746
|
}
|
|
@@ -108,7 +108,7 @@ export default class extends GoveeDevice {
|
|
|
108
108
|
case 'aa0e':
|
|
109
109
|
break
|
|
110
110
|
default: {
|
|
111
|
-
if (hexParts.length <
|
|
111
|
+
if (hexParts.length < 20) {
|
|
112
112
|
break
|
|
113
113
|
}
|
|
114
114
|
const tempInCen = Math.round((hexToDecimal(`0x${deviceFunction}`) + (this.accessory.context.offTemp / 100)) / 10) / 10
|
package/lib/platform.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
updateFailureError,
|
|
30
30
|
} from './utils/functions.js'
|
|
31
31
|
import platformLang from './utils/lang-en.js'
|
|
32
|
-
import { logUnknownData } from './utils/report-unknown.js'
|
|
32
|
+
import { isFirstReport, logUnknownData } from './utils/report-unknown.js'
|
|
33
33
|
import { parseDeviceUpdate } from './utils/response-parser.js'
|
|
34
34
|
|
|
35
35
|
const require = createRequire(import.meta.url)
|
|
@@ -825,8 +825,21 @@ export default class {
|
|
|
825
825
|
battery: goveeReading.battery,
|
|
826
826
|
source: 'BLE',
|
|
827
827
|
})
|
|
828
|
-
} else {
|
|
829
|
-
|
|
828
|
+
} else if (accessory) {
|
|
829
|
+
// A monitor's readings arrive over its own connection, so there is
|
|
830
|
+
// nothing to do with this one and nothing for the owner to act on
|
|
831
|
+
accessory.logDebug(`[BLE] ${platformLang.bleScanUnknown}`)
|
|
832
|
+
} else if (isFirstReport(`ble-reading|${goveeReading.address}`)) {
|
|
833
|
+
// A sensor broadcasts every few seconds, so say this once per session
|
|
834
|
+
// rather than on every advertisement. Only an address that belongs to
|
|
835
|
+
// this account is worth a warning - anything else may be a neighbour's
|
|
836
|
+
// device, which the owner cannot do anything about.
|
|
837
|
+
const line = `[BLE] ${platformLang.bleScanUnknown} [${goveeReading.address}].`
|
|
838
|
+
if (this.hasBleSensor(goveeReading.address)) {
|
|
839
|
+
this.log.warn(line)
|
|
840
|
+
} else {
|
|
841
|
+
this.log.debug(line)
|
|
842
|
+
}
|
|
830
843
|
}
|
|
831
844
|
} catch (err) {
|
|
832
845
|
this.log.warn('[BLE] error processing reading: %s', parseError(err))
|
|
@@ -33,6 +33,9 @@ export function applyUserConfig(platform, config) {
|
|
|
33
33
|
const logIncrease = (k, min) => {
|
|
34
34
|
platform.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgLow, min)
|
|
35
35
|
}
|
|
36
|
+
const logDecrease = (k, max) => {
|
|
37
|
+
platform.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgHigh, max)
|
|
38
|
+
}
|
|
36
39
|
const logQuotes = (k) => {
|
|
37
40
|
platform.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgQts)
|
|
38
41
|
}
|
|
@@ -77,6 +80,7 @@ export function applyUserConfig(platform, config) {
|
|
|
77
80
|
case 'disableDeviceLogging':
|
|
78
81
|
case 'ignoreMatter':
|
|
79
82
|
case 'lanDisable':
|
|
83
|
+
case 'openApiDisable':
|
|
80
84
|
if (typeof val === 'string') {
|
|
81
85
|
logQuotes(key)
|
|
82
86
|
}
|
|
@@ -86,7 +90,8 @@ export function applyUserConfig(platform, config) {
|
|
|
86
90
|
case 'bleRefreshTime':
|
|
87
91
|
case 'httpRefreshTime':
|
|
88
92
|
case 'lanRefreshTime':
|
|
89
|
-
case 'lanScanInterval':
|
|
93
|
+
case 'lanScanInterval':
|
|
94
|
+
case 'openApiRefreshTime': {
|
|
90
95
|
if (typeof val === 'string') {
|
|
91
96
|
logQuotes(key)
|
|
92
97
|
}
|
|
@@ -97,6 +102,9 @@ export function applyUserConfig(platform, config) {
|
|
|
97
102
|
} else if (intVal < platformConsts.minValues[key]) {
|
|
98
103
|
logIncrease(key, platformConsts.minValues[key])
|
|
99
104
|
platform.config[key] = platformConsts.minValues[key]
|
|
105
|
+
} else if (platformConsts.maxValues[key] !== undefined && intVal > platformConsts.maxValues[key]) {
|
|
106
|
+
logDecrease(key, platformConsts.maxValues[key])
|
|
107
|
+
platform.config[key] = platformConsts.maxValues[key]
|
|
100
108
|
} else {
|
|
101
109
|
platform.config[key] = intVal
|
|
102
110
|
}
|
package/lib/utils/constants.js
CHANGED
|
@@ -15,6 +15,8 @@ export default {
|
|
|
15
15
|
lanDisable: false,
|
|
16
16
|
lanRefreshTime: 30,
|
|
17
17
|
lanScanInterval: 60,
|
|
18
|
+
openApiDisable: false,
|
|
19
|
+
openApiRefreshTime: 30,
|
|
18
20
|
bleControlInterval: 5,
|
|
19
21
|
colourSafeMode: false,
|
|
20
22
|
lightDevices: [],
|
|
@@ -42,6 +44,7 @@ export default {
|
|
|
42
44
|
lanRefreshTime: 30,
|
|
43
45
|
lanScanInterval: 60,
|
|
44
46
|
lowBattThreshold: 20,
|
|
47
|
+
openApiRefreshTime: 30,
|
|
45
48
|
showAs: 'default',
|
|
46
49
|
},
|
|
47
50
|
|
|
@@ -54,6 +57,19 @@ export default {
|
|
|
54
57
|
lanRefreshTime: 10,
|
|
55
58
|
lanScanInterval: 30,
|
|
56
59
|
lowBattThreshold: 1,
|
|
60
|
+
openApiRefreshTime: 30,
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// A Node timer holds its delay in a signed 32-bit integer. Over 2147483647 ms
|
|
64
|
+
// it does not throw - it quietly becomes 1 ms, so any of these set past that
|
|
65
|
+
// point would poll a thousand times a second rather than slowly. 2073600
|
|
66
|
+
// seconds is 24 days, comfortably inside the limit once multiplied up.
|
|
67
|
+
maxValues: {
|
|
68
|
+
bleRefreshTime: 2073600,
|
|
69
|
+
httpRefreshTime: 2073600,
|
|
70
|
+
lanRefreshTime: 2073600,
|
|
71
|
+
lanScanInterval: 2073600,
|
|
72
|
+
openApiRefreshTime: 2073600,
|
|
57
73
|
},
|
|
58
74
|
|
|
59
75
|
allowed: {
|
package/lib/utils/lang-en.js
CHANGED
|
@@ -6,8 +6,8 @@ export default {
|
|
|
6
6
|
accTokenNoExist: 'does not exist, this is normally not an issue',
|
|
7
7
|
accTokenStoreErr: 'could not store access token as',
|
|
8
8
|
accTokenUserChange: 'username has changed',
|
|
9
|
-
alDisabled: 'adaptive lighting
|
|
10
|
-
alDisabledScene: 'adaptive lighting
|
|
9
|
+
alDisabled: 'adaptive lighting turned off, as the colour was changed outside HomeKit',
|
|
10
|
+
alDisabledScene: 'adaptive lighting turned off, as a scene was selected',
|
|
11
11
|
awsEventClose: 'close event',
|
|
12
12
|
awsEventConnect: 'connect event',
|
|
13
13
|
awsEventError: 'error event',
|
|
@@ -34,6 +34,7 @@ export default {
|
|
|
34
34
|
cfgIgn: 'is not configured correctly so ignoring',
|
|
35
35
|
cfgIgnItem: 'has an invalid entry which will be ignored',
|
|
36
36
|
cfgItem: 'Config entry',
|
|
37
|
+
cfgHigh: 'is set too high so reducing to',
|
|
37
38
|
cfgLow: 'is set too low so increasing to',
|
|
38
39
|
cfgRmv: 'is unused and can be removed',
|
|
39
40
|
cfgQts: 'should not have quotes around its entry',
|
package/package.json
CHANGED