@homebridge-plugins/homebridge-govee 10.12.1

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +1937 -0
  2. package/LICENSE +21 -0
  3. package/README.md +72 -0
  4. package/config.schema.json +1727 -0
  5. package/eslint.config.js +49 -0
  6. package/lib/connection/aws.js +174 -0
  7. package/lib/connection/ble.js +208 -0
  8. package/lib/connection/cert/AmazonRootCA1.pem +20 -0
  9. package/lib/connection/http.js +240 -0
  10. package/lib/connection/lan.js +284 -0
  11. package/lib/device/cooler-single.js +300 -0
  12. package/lib/device/dehumidifier-H7150.js +182 -0
  13. package/lib/device/dehumidifier-H7151.js +157 -0
  14. package/lib/device/diffuser-H7161.js +117 -0
  15. package/lib/device/diffuser-H7162.js +117 -0
  16. package/lib/device/fan-H7100.js +274 -0
  17. package/lib/device/fan-H7101.js +330 -0
  18. package/lib/device/fan-H7102.js +274 -0
  19. package/lib/device/fan-H7105.js +503 -0
  20. package/lib/device/fan-H7106.js +274 -0
  21. package/lib/device/fan-H7111.js +335 -0
  22. package/lib/device/heater-single.js +300 -0
  23. package/lib/device/heater1a.js +353 -0
  24. package/lib/device/heater1b.js +616 -0
  25. package/lib/device/heater2.js +838 -0
  26. package/lib/device/humidifier-H7140.js +224 -0
  27. package/lib/device/humidifier-H7141.js +257 -0
  28. package/lib/device/humidifier-H7142.js +522 -0
  29. package/lib/device/humidifier-H7143.js +157 -0
  30. package/lib/device/humidifier-H7148.js +157 -0
  31. package/lib/device/humidifier-H7160.js +446 -0
  32. package/lib/device/ice-maker-H7162.js +46 -0
  33. package/lib/device/index.js +105 -0
  34. package/lib/device/kettle.js +269 -0
  35. package/lib/device/light-switch.js +86 -0
  36. package/lib/device/light.js +617 -0
  37. package/lib/device/outlet-double.js +121 -0
  38. package/lib/device/outlet-single.js +172 -0
  39. package/lib/device/outlet-triple.js +160 -0
  40. package/lib/device/purifier-H7120.js +336 -0
  41. package/lib/device/purifier-H7121.js +336 -0
  42. package/lib/device/purifier-H7122.js +449 -0
  43. package/lib/device/purifier-H7123.js +411 -0
  44. package/lib/device/purifier-H7124.js +411 -0
  45. package/lib/device/purifier-H7126.js +296 -0
  46. package/lib/device/purifier-H7127.js +296 -0
  47. package/lib/device/purifier-H712C.js +296 -0
  48. package/lib/device/purifier-single.js +119 -0
  49. package/lib/device/sensor-button.js +22 -0
  50. package/lib/device/sensor-contact.js +22 -0
  51. package/lib/device/sensor-leak.js +87 -0
  52. package/lib/device/sensor-monitor.js +190 -0
  53. package/lib/device/sensor-presence.js +53 -0
  54. package/lib/device/sensor-thermo.js +144 -0
  55. package/lib/device/sensor-thermo4.js +55 -0
  56. package/lib/device/switch-double.js +121 -0
  57. package/lib/device/switch-single.js +95 -0
  58. package/lib/device/switch-triple.js +160 -0
  59. package/lib/device/tap-single.js +108 -0
  60. package/lib/device/template.js +43 -0
  61. package/lib/device/tv-single.js +84 -0
  62. package/lib/device/valve-single.js +155 -0
  63. package/lib/fakegato/LICENSE +21 -0
  64. package/lib/fakegato/fakegato-history.js +814 -0
  65. package/lib/fakegato/fakegato-storage.js +108 -0
  66. package/lib/fakegato/fakegato-timer.js +125 -0
  67. package/lib/fakegato/uuid.js +27 -0
  68. package/lib/homebridge-ui/public/index.html +433 -0
  69. package/lib/homebridge-ui/server.js +10 -0
  70. package/lib/index.js +8 -0
  71. package/lib/platform.js +1967 -0
  72. package/lib/utils/colour.js +564 -0
  73. package/lib/utils/constants.js +579 -0
  74. package/lib/utils/custom-chars.js +225 -0
  75. package/lib/utils/eve-chars.js +68 -0
  76. package/lib/utils/functions.js +117 -0
  77. package/lib/utils/lang-en.js +131 -0
  78. package/package.json +75 -0
@@ -0,0 +1,617 @@
1
+ import {
2
+ hs2rgb,
3
+ k2rgb,
4
+ m2hs,
5
+ rgb2hs,
6
+ } from '../utils/colour.js'
7
+ import platformConsts from '../utils/constants.js'
8
+ import {
9
+ generateRandomString,
10
+ hasProperty,
11
+ parseError,
12
+ sleep,
13
+ } from '../utils/functions.js'
14
+ import platformLang from '../utils/lang-en.js'
15
+
16
+ export default class {
17
+ constructor(platform, accessory) {
18
+ // Set up variables from the platform
19
+ this.cusChar = platform.cusChar
20
+ this.hapChar = platform.api.hap.Characteristic
21
+ this.hapErr = platform.api.hap.HapStatusError
22
+ this.hapServ = platform.api.hap.Service
23
+ this.platform = platform
24
+
25
+ // Set up variables from the accessory
26
+ this.accessory = accessory
27
+ this.colourSafeMode = platform.config.colourSafeMode
28
+ this.minKelvin = accessory.context?.supportedCmdsOpts?.colorTem?.range?.min || 2000
29
+ this.maxKelvin = accessory.context?.supportedCmdsOpts?.colorTem?.range?.max || 9000
30
+ this.isBLEOnly = !accessory.context.useAWSControl && !accessory.context.useLANControl
31
+
32
+ // Set up custom variables for this device type
33
+ const deviceConf = platform.deviceConf[accessory.context.gvDeviceId] || {}
34
+ this.alShift = deviceConf.adaptiveLightingShift || platformConsts.defaultValues.adaptiveLightingShift
35
+ this.brightStep = deviceConf.brightnessStep
36
+ ? Math.min(deviceConf.brightnessStep, 100)
37
+ : platformConsts.defaultValues.brightnessStep
38
+
39
+ // Remove any switch service if it exists
40
+ if (accessory.getService(this.hapServ.Switch)) {
41
+ accessory.removeService(accessory.getService(this.hapServ.Switch))
42
+ }
43
+
44
+ // Add the main lightbulb service if it doesn't already exist
45
+ this.service = this.accessory.getService(this.hapServ.Lightbulb)
46
+ || this.accessory.addService(this.hapServ.Lightbulb)
47
+
48
+ // If adaptive lighting has just been disabled then remove and re-add service to hide AL icon
49
+ if ((this.colourSafeMode || this.alShift === -1) && this.accessory.context.adaptiveLighting) {
50
+ this.accessory.removeService(this.service)
51
+ this.service = this.accessory.addService(this.hapServ.Lightbulb)
52
+ this.accessory.context.adaptiveLighting = false
53
+ }
54
+
55
+ // Setup custom characteristics for different scenes and modes
56
+ this.usedCodes = [];
57
+
58
+ [
59
+ 'DiyMode',
60
+ 'DiyModeTwo',
61
+ 'DiyModeThree',
62
+ 'DiyModeFour',
63
+ 'MusicMode',
64
+ 'MusicModeTwo',
65
+ 'Scene',
66
+ 'SceneTwo',
67
+ 'SceneThree',
68
+ 'SceneFour',
69
+ 'Segmented',
70
+ 'SegmentedTwo',
71
+ 'SegmentedThree',
72
+ 'SegmentedFour',
73
+ 'VideoMode',
74
+ 'VideoModeTwo',
75
+ ].forEach((charName) => {
76
+ const confName = charName.charAt(0).toLowerCase() + charName.slice(1)
77
+ const confCode = deviceConf[confName]
78
+
79
+ // Check if any code has been entered in the config by the user
80
+ if (confCode?.sceneCode) {
81
+ const { bleCode, sceneCode } = confCode
82
+
83
+ // Add to the global enabled scenes list
84
+ this.usedCodes.push(charName)
85
+
86
+ // Add the characteristic if not already
87
+ if (confCode?.showAs === 'switch') {
88
+ // Remove the Eve switch if exists
89
+ if (this.service.testCharacteristic(this.cusChar[charName])) {
90
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.cusChar[charName]))
91
+ }
92
+
93
+ // Add the accessory service switch
94
+ if (!this.accessory.getService(charName)) {
95
+ this.accessory.addService(this.hapServ.Switch, charName, charName)
96
+ }
97
+
98
+ // Add the set handler and also mark all as off when initialising accessory
99
+ this.accessory.getService(charName)
100
+ .getCharacteristic(this.hapChar.On)
101
+ .onSet(async (value) => {
102
+ await this.internalSceneUpdate(charName, sceneCode, bleCode, value, true)
103
+ })
104
+ .updateValue(false)
105
+ } else {
106
+ // Remove the accessory service switch if exists
107
+ if (this.accessory.getService(charName)) {
108
+ this.accessory.removeService(this.accessory.getService(charName))
109
+ }
110
+
111
+ // Add the Eve switch
112
+ if (!this.service.testCharacteristic(this.cusChar[charName])) {
113
+ this.service.addCharacteristic(this.cusChar[charName])
114
+ }
115
+
116
+ // Add the set handler and also mark all as off when initialising accessory
117
+ this.service
118
+ .getCharacteristic(this.cusChar[charName])
119
+ .onSet(async (value) => {
120
+ await this.internalSceneUpdate(charName, sceneCode, bleCode, value)
121
+ })
122
+ .updateValue(false)
123
+ }
124
+ } else {
125
+ // If here then either code is invalid or has been removed, so remove the characteristic
126
+
127
+ if (this.service.testCharacteristic(this.cusChar[charName])) {
128
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.cusChar[charName]))
129
+ }
130
+ }
131
+ })
132
+
133
+ this.hasScenes = this.usedCodes.length > 0
134
+
135
+ // Add the colour mode characteristic if at least one other scene/mode is exposed
136
+ if (this.hasScenes) {
137
+ // Add the colour mode characteristic if not already
138
+ if (!this.service.testCharacteristic(this.cusChar.ColourMode)) {
139
+ this.service.addCharacteristic(this.cusChar.ColourMode)
140
+ }
141
+
142
+ // Add the set handler and also mark as off when initialising accessory
143
+ this.service
144
+ .getCharacteristic(this.cusChar.ColourMode)
145
+ .onSet(async (value) => {
146
+ if (value) {
147
+ await this.internalColourUpdate(this.cacheHue, true)
148
+ }
149
+ })
150
+ .updateValue(false)
151
+ } else if (this.service.testCharacteristic(this.cusChar.ColourMode)) {
152
+ // Remove the characteristic if it exists already (no need for it)
153
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.cusChar.ColourMode))
154
+ }
155
+
156
+ // Add the set handler to the lightbulb on/off characteristic
157
+ this.service.getCharacteristic(this.hapChar.On).onSet(async (value) => {
158
+ await this.internalStateUpdate(value)
159
+ })
160
+ this.cacheState = this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
161
+
162
+ // Add the set handler to the lightbulb brightness characteristic
163
+ this.service
164
+ .getCharacteristic(this.hapChar.Brightness)
165
+ .setProps({ minStep: this.brightStep })
166
+ .onSet(async (value) => {
167
+ await this.internalBrightnessUpdate(value)
168
+ })
169
+ this.cacheBright = this.service.getCharacteristic(this.hapChar.Brightness).value
170
+ this.cacheBrightRaw = this.cacheBright
171
+
172
+ // Add the set handler to the lightbulb hue characteristic
173
+ this.service.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
174
+ await this.internalColourUpdate(value)
175
+ })
176
+ this.cacheHue = this.service.getCharacteristic(this.hapChar.Hue).value
177
+ this.cacheSat = this.service.getCharacteristic(this.hapChar.Saturation).value
178
+
179
+ // Add the set handler to the lightbulb cct characteristic
180
+ if (this.colourSafeMode) {
181
+ if (this.service.testCharacteristic(this.hapChar.ColorTemperature)) {
182
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.hapChar.ColorTemperature))
183
+ }
184
+ this.cacheMired = 0
185
+ } else {
186
+ this.service.getCharacteristic(this.hapChar.ColorTemperature).onSet(async (value) => {
187
+ await this.internalCTUpdate(value)
188
+ })
189
+ this.cacheMired = this.service.getCharacteristic(this.hapChar.ColorTemperature).value
190
+ }
191
+
192
+ // Set up the adaptive lighting controller if not disabled by user
193
+ if (!this.colourSafeMode && this.alShift !== -1) {
194
+ this.alController = new platform.api.hap.AdaptiveLightingController(this.service, {
195
+ customTemperatureAdjustment: this.alShift,
196
+ })
197
+ this.accessory.configureController(this.alController)
198
+ this.accessory.context.adaptiveLighting = true
199
+ }
200
+
201
+ // Output the customised options to the log
202
+ const useAWSControl = accessory.context.useAWSControl ? 'enabled' : 'disabled'
203
+ const useBLEControl = accessory.context.useBLEControl ? 'enabled' : 'disabled'
204
+ const useLANControl = accessory.context.useLANControl ? 'enabled' : 'disabled'
205
+ const opts = JSON.stringify({
206
+ adaptiveLightingShift: this.alShift,
207
+ aws: accessory.context.hasAWSControl ? useAWSControl : 'unsupported',
208
+ ble: accessory.context.hasBLEControl ? useBLEControl : 'unsupported',
209
+ brightnessStep: this.brightStep,
210
+ colourSafeMode: this.colourSafeMode,
211
+ lan: accessory.context.hasLANControl ? useLANControl : 'unsupported',
212
+ })
213
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
214
+ this.initialised = true
215
+ }
216
+
217
+ async internalStateUpdate(value) {
218
+ try {
219
+ const newValue = value ? 'on' : 'off'
220
+
221
+ // Don't continue if the new value is the same as before
222
+ if (newValue === this.cacheState) {
223
+ return
224
+ }
225
+
226
+ // Await slightly longer than brightness and colour so on/off is sent last
227
+ await sleep(400)
228
+
229
+ // Send the request to the platform sender function
230
+ await this.platform.sendDeviceUpdate(this.accessory, {
231
+ cmd: 'state',
232
+ value: newValue,
233
+ })
234
+
235
+ // Cache the new state and log if appropriate
236
+ if (this.cacheState !== newValue) {
237
+ this.cacheState = newValue
238
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
239
+ }
240
+ } catch (err) {
241
+ // Catch any errors during the process
242
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
243
+
244
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
245
+ setTimeout(() => {
246
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
247
+ }, 2000)
248
+ throw new this.hapErr(-70402)
249
+ }
250
+ }
251
+
252
+ async internalBrightnessUpdate(value) {
253
+ try {
254
+ // This acts like a debounce function when endlessly sliding the brightness scale
255
+ const updateKeyBright = generateRandomString(5)
256
+ this.updateKeyBright = updateKeyBright
257
+ await sleep(350)
258
+ if (updateKeyBright !== this.updateKeyBright) {
259
+ return
260
+ }
261
+
262
+ // Don't continue if the new value is the same as before
263
+ if (value === this.cacheBright) {
264
+ return
265
+ }
266
+
267
+ // Send the request to the platform sender function
268
+ await this.platform.sendDeviceUpdate(this.accessory, {
269
+ cmd: 'brightness',
270
+ value,
271
+ })
272
+
273
+ // Govee considers 0% brightness to be off
274
+ if (value === 0) {
275
+ setTimeout(() => {
276
+ this.cacheState = 'off'
277
+ if (this.service.getCharacteristic(this.hapChar.On).value) {
278
+ this.service.updateCharacteristic(this.hapChar.On, false)
279
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
280
+ }
281
+ this.service.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
282
+ }, 1500)
283
+ return
284
+ }
285
+
286
+ // Cache the new state and log if appropriate
287
+ if (this.cacheBright !== value) {
288
+ this.cacheBright = value
289
+ this.accessory.log(`${platformLang.curBright} [${value}%]`)
290
+ }
291
+ } catch (err) {
292
+ // Catch any errors during the process
293
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
294
+
295
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
296
+ setTimeout(() => {
297
+ this.service.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
298
+ }, 2000)
299
+ throw new this.hapErr(-70402)
300
+ }
301
+ }
302
+
303
+ async internalColourUpdate(value, force = false) {
304
+ try {
305
+ // This acts like a debounce function when endlessly sliding the colour wheel
306
+ const updateKeyColour = generateRandomString(5)
307
+ this.updateKeyColour = updateKeyColour
308
+ await sleep(300)
309
+ if (updateKeyColour !== this.updateKeyColour) {
310
+ return
311
+ }
312
+
313
+ if (!this.colourSafeMode) {
314
+ // Updating the cct to the lowest value mimics native adaptive lighting
315
+ this.service.updateCharacteristic(this.hapChar.ColorTemperature, 140)
316
+ }
317
+
318
+ // Don't continue if the new value is the same as before
319
+ const currentSat = this.service.getCharacteristic(this.hapChar.Saturation).value
320
+ const newRGB = hs2rgb(value, currentSat)
321
+ if (
322
+ !force
323
+ && newRGB[0] === this.cacheR
324
+ && newRGB[1] === this.cacheG
325
+ && newRGB[2] === this.cacheB
326
+ ) {
327
+ return
328
+ }
329
+
330
+ // Send the request to the platform sender function
331
+ await this.platform.sendDeviceUpdate(this.accessory, {
332
+ cmd: 'color',
333
+ value: {
334
+ r: newRGB[0],
335
+ g: newRGB[1],
336
+ b: newRGB[2],
337
+ },
338
+ })
339
+
340
+ // Switch off any custom mode/scene characteristics and turn the on switch to on
341
+ if (this.hasScenes) {
342
+ setTimeout(() => {
343
+ this.service.updateCharacteristic(this.hapChar.On, true)
344
+ this.service.updateCharacteristic(this.cusChar.ColourMode, true)
345
+ this.usedCodes.forEach((thisCharName) => {
346
+ if (this.service.testCharacteristic(this.cusChar[thisCharName])) {
347
+ this.service.updateCharacteristic(this.cusChar[thisCharName], false)
348
+ }
349
+ if (this.accessory.getService(thisCharName)) {
350
+ this.accessory.getService(thisCharName).updateCharacteristic(this.hapChar.On, false)
351
+ }
352
+ })
353
+ }, 1000)
354
+ }
355
+
356
+ // Cache the new state and log if appropriate
357
+ this.cacheHue = value
358
+ this.cacheKelvin = 0
359
+ this.cacheScene = ''
360
+ if (this.cacheR !== newRGB[0] || this.cacheG !== newRGB[1] || this.cacheB !== newRGB[2]) {
361
+ [this.cacheR, this.cacheG, this.cacheB] = newRGB
362
+ this.accessory.log(`${platformLang.curColour} [rgb ${this.cacheR} ${this.cacheG} ${this.cacheB}]`)
363
+ }
364
+ } catch (err) {
365
+ // Catch any errors during the process
366
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
367
+
368
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
369
+ setTimeout(() => {
370
+ this.service.updateCharacteristic(this.hapChar.Hue, this.cacheHue)
371
+ }, 2000)
372
+ throw new this.hapErr(-70402)
373
+ }
374
+ }
375
+
376
+ async internalCTUpdate(value) {
377
+ try {
378
+ // This acts like a debounce function when endlessly sliding the colour wheel
379
+ const updateKeyCT = generateRandomString(5)
380
+ this.updateKeyCT = updateKeyCT
381
+ await sleep(300)
382
+ if (updateKeyCT !== this.updateKeyCT) {
383
+ return
384
+ }
385
+
386
+ // Convert mired to kelvin to nearest 100 (Govee seems to need this)
387
+ const kelvin = Math.round(1000000 / value / 100) * 100
388
+
389
+ // Check and increase/decrease kelvin to range of device
390
+ const k = Math.min(Math.max(kelvin, this.minKelvin), this.maxKelvin)
391
+
392
+ // Don't continue if the new value is the same as before
393
+ if (this.cacheState !== 'on' || this.cacheKelvin === k) {
394
+ if (this.alController?.isAdaptiveLightingActive?.()) {
395
+ this.accessory.logDebug(`${platformLang.skippingAL} [${k}K /${value}M]`)
396
+ }
397
+ return
398
+ }
399
+
400
+ // Updating the hue/sat to the corresponding values mimics native adaptive lighting
401
+ const hs = m2hs(value)
402
+ this.service.updateCharacteristic(this.hapChar.Hue, hs[0])
403
+ this.service.updateCharacteristic(this.hapChar.Saturation, hs[1])
404
+
405
+ // Convert kelvin to rgb to use in case device doesn't support colour temperature
406
+ const rgb = k2rgb(k)
407
+
408
+ // Set up the params object to send
409
+ const objToSend = {}
410
+
411
+ // For BLE only models, convert to RGB, otherwise send kelvin value
412
+ // TODO we can look at this in the future
413
+ if (this.isBLEOnly) {
414
+ objToSend.cmd = 'color'
415
+ objToSend.value = { r: rgb[0], g: rgb[1], b: rgb[2] }
416
+ } else {
417
+ objToSend.cmd = 'colorTem'
418
+ objToSend.value = k
419
+ }
420
+
421
+ // Send the request to the platform sender function
422
+ await this.platform.sendDeviceUpdate(this.accessory, objToSend)
423
+
424
+ // Switch off any custom mode/scene characteristics and turn the on switch to on
425
+ if (this.hasScenes) {
426
+ setTimeout(() => {
427
+ this.service.updateCharacteristic(this.hapChar.On, true)
428
+ this.service.updateCharacteristic(this.cusChar.ColourMode, true)
429
+ this.usedCodes.forEach((thisCharName) => {
430
+ if (this.service.testCharacteristic(this.cusChar[thisCharName])) {
431
+ this.service.updateCharacteristic(this.cusChar[thisCharName], false)
432
+ }
433
+ if (this.accessory.getService(thisCharName)) {
434
+ this.accessory.getService(thisCharName).updateCharacteristic(this.hapChar.On, false)
435
+ }
436
+ })
437
+ }, 1000)
438
+ }
439
+
440
+ // Cache the new state and log if appropriate
441
+ [this.cacheR, this.cacheG, this.cacheB] = rgb
442
+ this.cacheMired = value
443
+ this.cacheScene = ''
444
+ if (this.cacheKelvin !== k) {
445
+ this.cacheKelvin = k
446
+ if (this.alController?.isAdaptiveLightingActive?.()) {
447
+ this.accessory.log(`${platformLang.curColour} [${k}K / ${value}M] ${platformLang.viaAL}`)
448
+ } else {
449
+ this.accessory.log(`${platformLang.curColour} [${k}K / ${value}M]`)
450
+ }
451
+ }
452
+ } catch (err) {
453
+ // Catch any errors during the process
454
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
455
+
456
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
457
+ setTimeout(() => {
458
+ this.service.updateCharacteristic(this.hapChar.ColorTemperature, this.cacheMired)
459
+ }, 2000)
460
+ throw new this.hapErr(-70402)
461
+ }
462
+ }
463
+
464
+ async internalSceneUpdate(charName, awsCode, bleCode, value, isService = false) {
465
+ try {
466
+ // Don't continue if command is to turn off - we should turn off by changing to a colour mode instead, or another scene
467
+ if (!value) {
468
+ return
469
+ }
470
+
471
+ // Send the request to the platform sender function
472
+ await this.platform.sendDeviceUpdate(this.accessory, {
473
+ cmd: 'rgbScene',
474
+ value: [awsCode, bleCode],
475
+ })
476
+
477
+ // Disable adaptive lighting if it's on already
478
+ if (!this.colourSafeMode && this.alController?.isAdaptiveLightingActive?.()) {
479
+ this.alController.disableAdaptiveLighting()
480
+ this.accessory.log(platformLang.alDisabledScene)
481
+ }
482
+
483
+ // Log the scene change
484
+ if (this.cacheScene !== charName) {
485
+ this.cacheScene = charName
486
+ this.accessory.log(`${platformLang.curScene} [${this.cacheScene}]`)
487
+ }
488
+
489
+ // Turn all the characteristics off and turn the on switch to on
490
+ setTimeout(() => {
491
+ this.service.updateCharacteristic(this.hapChar.On, true)
492
+ this.service.updateCharacteristic(this.cusChar.ColourMode, false)
493
+ this.usedCodes.forEach((thisCharName) => {
494
+ if (thisCharName !== charName) {
495
+ if (this.service.testCharacteristic(this.cusChar[thisCharName])) {
496
+ this.service.updateCharacteristic(this.cusChar[thisCharName], false)
497
+ }
498
+ if (this.accessory.getService(thisCharName)) {
499
+ this.accessory.getService(thisCharName).updateCharacteristic(this.hapChar.On, false)
500
+ }
501
+ }
502
+ })
503
+ }, 1000)
504
+ } catch (err) {
505
+ // Catch any errors during the process
506
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
507
+
508
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
509
+ setTimeout(() => {
510
+ if (isService) {
511
+ this.accessory.getService(charName).updateCharacteristic(this.hapChar.On, false)
512
+ } else {
513
+ this.service.updateCharacteristic(this.cusChar[charName], false)
514
+ }
515
+ }, 2000)
516
+ throw new this.hapErr(-70402)
517
+ }
518
+ }
519
+
520
+ externalUpdate(params) {
521
+ // Return if not initialised
522
+ if (!this.initialised) {
523
+ return
524
+ }
525
+
526
+ // Check to see if the provided state is different from the cached value
527
+ if (params.state && params.state !== this.cacheState) {
528
+ // State is different so update Homebridge with new values
529
+ this.cacheState = params.state
530
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
531
+
532
+ // Log the change
533
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
534
+ }
535
+
536
+ // Check to see if the provided brightness is different from the cached value
537
+ if (hasProperty(params, 'brightness') && params.brightness !== this.cacheBrightRaw) {
538
+ // Brightness is different so update Homebridge with new values
539
+ this.cacheBrightRaw = params.brightness
540
+
541
+ // Govee considers brightness 0 as OFF so change brightness to 1 if light is on
542
+ this.cacheBright = this.cacheState === 'on' ? Math.max(this.cacheBrightRaw, 1) : this.cacheBrightRaw
543
+ this.service.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
544
+
545
+ // Log the change
546
+ this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
547
+ }
548
+
549
+ // Check to see if the provided colour is different from the cached state
550
+ if (params.kelvin || params.rgb) {
551
+ // Colour can be provided in rgb or kelvin so either way convert to hs for later
552
+ let hs
553
+ let rgb
554
+ let mired
555
+ let colourChange = false
556
+ let sigColourChange = false
557
+ if (params.kelvin) {
558
+ mired = Math.round(1000000 / params.kelvin)
559
+ hs = m2hs(mired)
560
+ rgb = hs2rgb(hs[0], hs[1])
561
+
562
+ // Check for a colour change
563
+ if (params.kelvin !== this.cacheKelvin) {
564
+ colourChange = true
565
+
566
+ // Check for a significant colour change
567
+ const kelvinDiff = Math.abs(params.kelvin - this.cacheKelvin)
568
+ if (kelvinDiff > 100) {
569
+ sigColourChange = true
570
+ }
571
+ }
572
+ } else {
573
+ rgb = [params.rgb.r, params.rgb.g, params.rgb.b]
574
+ hs = rgb2hs(rgb[0], rgb[1], rgb[2])
575
+
576
+ // Check for a colour change
577
+ if (hs[0] !== this.cacheHue) {
578
+ colourChange = true
579
+
580
+ // Check for a significant colour change
581
+ const rgbDiff = Math.abs(rgb[0] - this.cacheR)
582
+ + Math.abs(rgb[1] - this.cacheG)
583
+ + Math.abs(rgb[2] - this.cacheB)
584
+ if (rgbDiff > 50) {
585
+ sigColourChange = true
586
+ }
587
+ }
588
+ }
589
+
590
+ // Perform the check against the cache
591
+ if (colourChange) {
592
+ // Colour is different so update Homebridge with new values
593
+ this.service.updateCharacteristic(this.hapChar.Hue, hs[0])
594
+ this.service.updateCharacteristic(this.hapChar.Saturation, hs[1]);
595
+ [this.cacheR, this.cacheG, this.cacheB] = rgb;
596
+ [this.cacheHue] = hs
597
+
598
+ if (mired) {
599
+ if (!this.colourSafeMode) {
600
+ this.service.updateCharacteristic(this.hapChar.ColorTemperature, mired)
601
+ }
602
+ this.cacheMired = mired
603
+ this.cacheKelvin = params.kelvin
604
+ this.accessory.log(`${platformLang.curColour} [${params.kelvin}K / ${mired}M]`)
605
+ } else {
606
+ this.accessory.log(`${platformLang.curColour} [rgb ${this.cacheR} ${this.cacheG} ${this.cacheB}]`)
607
+ }
608
+
609
+ // If the difference is significant then disable adaptive lighting
610
+ if (!this.colourSafeMode && this.alController?.isAdaptiveLightingActive?.() && sigColourChange) {
611
+ this.alController.disableAdaptiveLighting()
612
+ this.accessory.log(platformLang.alDisabled)
613
+ }
614
+ }
615
+ }
616
+ }
617
+ }