@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,522 @@
1
+ import { hs2rgb, rgb2hs } from '../utils/colour.js'
2
+ import {
3
+ base64ToHex,
4
+ generateCodeFromHexValues,
5
+ generateRandomString,
6
+ getTwoItemPosition,
7
+ hexToDecimal,
8
+ hexToTwoItems,
9
+ parseError,
10
+ sleep,
11
+ } from '../utils/functions.js'
12
+ import platformLang from '../utils/lang-en.js'
13
+
14
+ /*
15
+ H7142
16
+ {
17
+ "mode": {
18
+ "options": [
19
+ {
20
+ "name": "Custom",
21
+ "value": 2
22
+ },
23
+ {
24
+ "name": "Auto",
25
+ "value": 3
26
+ }
27
+ ]
28
+ },
29
+ "gear": {
30
+ "options": [
31
+ {
32
+ "name": "gear",
33
+ "value": [
34
+ 1,
35
+ 2,
36
+ 3,
37
+ 4,
38
+ 5,
39
+ 6,
40
+ 7,
41
+ 8,
42
+ 9
43
+ ]
44
+ }
45
+ ]
46
+ }
47
+ }
48
+ */
49
+ export default class {
50
+ constructor(platform, accessory) {
51
+ // Set up variables from the platform
52
+ this.hapChar = platform.api.hap.Characteristic
53
+ this.hapErr = platform.api.hap.HapStatusError
54
+ this.hapServ = platform.api.hap.Service
55
+ this.platform = platform
56
+
57
+ // Set up variables from the accessory
58
+ this.accessory = accessory
59
+
60
+ // Rotation speed to value in {1, 2, ..., 8}
61
+ this.speed2Value = speed => Math.min(Math.max(Number.parseInt(Math.round(speed / 10), 10), 1), 9)
62
+
63
+ // Speed codes
64
+ this.value2Code = {
65
+ 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
66
+ 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
67
+ 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
68
+ 4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
69
+ 5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
70
+ 6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
71
+ 7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
72
+ 8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
73
+ 9: 'MwUBCQAAAAAAAAAAAAAAAAAAAD4=',
74
+ }
75
+
76
+ // Add the fan service if it doesn't already exist
77
+ this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
78
+
79
+ // Add humidity sensor service if it doesn't already exist
80
+ this.humiService = this.accessory.getService(this.hapServ.HumiditySensor)
81
+ || this.accessory.addService(this.hapServ.HumiditySensor)
82
+
83
+ // Add the night light service if it doesn't already exist
84
+ this.lightService = this.accessory.getService(this.hapServ.Lightbulb)
85
+ || this.accessory.addService(this.hapServ.Lightbulb)
86
+
87
+ this.cacheHumi = this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value
88
+
89
+ // Add the set handler to the fan on/off characteristic
90
+ this.service
91
+ .getCharacteristic(this.hapChar.On)
92
+ .onSet(async value => this.internalStateUpdate(value))
93
+ this.cacheState = this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
94
+ this.cacheUV = this.cacheState
95
+
96
+ // Add the set handler to the fan rotation speed characteristic
97
+ this.service
98
+ .getCharacteristic(this.hapChar.RotationSpeed)
99
+ .setProps({
100
+ minStep: 10,
101
+ validValues: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
102
+ })
103
+ .onSet(async value => this.internalSpeedUpdate(value))
104
+ this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
105
+ this.cacheSpeedRaw = `0${this.cacheSpeed / 10}` // example '02' for 20%
106
+
107
+ // Add the set handler to the lightbulb on/off characteristic
108
+ this.lightService.getCharacteristic(this.hapChar.On).onSet(async (value) => {
109
+ await this.internalLightStateUpdate(value)
110
+ })
111
+ this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
112
+
113
+ // Add the set handler to the lightbulb brightness characteristic
114
+ this.lightService
115
+ .getCharacteristic(this.hapChar.Brightness)
116
+ .onSet(async (value) => {
117
+ await this.internalBrightnessUpdate(value)
118
+ })
119
+ this.cacheBright = this.lightService.getCharacteristic(this.hapChar.Brightness).value
120
+
121
+ // Add the set handler to the lightbulb hue characteristic
122
+ this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
123
+ await this.internalColourUpdate(value)
124
+ })
125
+ this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
126
+ this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
127
+
128
+ // Output the customised options to the log
129
+ const opts = JSON.stringify({})
130
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
131
+ }
132
+
133
+ async internalStateUpdate(value) {
134
+ try {
135
+ const newValue = value ? 'on' : 'off'
136
+
137
+ // Don't continue if the new value is the same as before
138
+ if (this.cacheState === newValue) {
139
+ return
140
+ }
141
+
142
+ // Send the request to the platform sender function
143
+ await this.platform.sendDeviceUpdate(this.accessory, {
144
+ cmd: 'stateHumi',
145
+ value: value ? 1 : 0,
146
+ })
147
+
148
+ // If turning on, also turn on the uv light
149
+ if (value && this.cacheUV === 'off') {
150
+ await sleep(200)
151
+
152
+ // Send the uv request to the platform sender function
153
+ await this.platform.sendDeviceUpdate(this.accessory, {
154
+ cmd: 'ptReal',
155
+ value: 'MxoBAAAAAAAAAAAAAAAAAAAAACg=',
156
+ })
157
+ }
158
+
159
+ // Cache the new state and log if appropriate
160
+ if (this.cacheState !== newValue) {
161
+ this.cacheState = newValue
162
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
163
+ }
164
+ if (this.cacheUV !== newValue) {
165
+ this.cacheUV = newValue
166
+ this.accessory.log(`current uv light [${this.cacheUV}]`)
167
+ }
168
+
169
+ // Also turn the light off if turning off
170
+ if (!value && this.cacheLightState === 'on') {
171
+ this.lightService.updateCharacteristic(this.hapChar.On, false)
172
+ this.accessory.log(`current light state [${this.cacheLightState}]`)
173
+ }
174
+ } catch (err) {
175
+ // Catch any errors during the process
176
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
177
+
178
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
179
+ setTimeout(() => {
180
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
181
+ }, 2000)
182
+ throw new this.hapErr(-70402)
183
+ }
184
+ }
185
+
186
+ async internalSpeedUpdate(value) {
187
+ try {
188
+ // Don't continue if the speed is 0
189
+ if (value === 0) {
190
+ return
191
+ }
192
+
193
+ // Get the single Govee value {1, 2, ..., 8}
194
+ const newValue = this.speed2Value(value)
195
+
196
+ // Don't continue if the speed value won't have effect
197
+ if (newValue * 10 === this.cacheSpeed) {
198
+ return
199
+ }
200
+
201
+ // Get the scene code for this value
202
+ const newCode = this.value2Code[newValue]
203
+
204
+ this.accessory.log(newCode)
205
+
206
+ // Send the request to the platform sender function
207
+ await this.platform.sendDeviceUpdate(this.accessory, {
208
+ cmd: 'ptReal',
209
+ value: newCode,
210
+ })
211
+
212
+ // Cache the new state and log if appropriate
213
+ this.cacheSpeed = newValue * 10
214
+ this.accessory.log(`${platformLang.curSpeed} [${newValue}]`)
215
+ } catch (err) {
216
+ // Catch any errors during the process
217
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
218
+
219
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
220
+ setTimeout(() => {
221
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
222
+ }, 2000)
223
+ throw new this.hapErr(-70402)
224
+ }
225
+ }
226
+
227
+ async internalLightStateUpdate(value) {
228
+ try {
229
+ const newValue = value ? 'on' : 'off'
230
+
231
+ // Don't continue if the new value is the same as before
232
+ if (this.cacheLightState === newValue) {
233
+ return
234
+ }
235
+
236
+ // Generate the hex values for the code
237
+ let hexValues
238
+ if (value) {
239
+ // Calculate current RGB values
240
+ const newRGB = hs2rgb(
241
+ this.lightService.getCharacteristic(this.hapChar.Hue).value,
242
+ this.lightService.getCharacteristic(this.hapChar.Saturation).value,
243
+ )
244
+ hexValues = [0x33, 0x1B, 0x01, this.cacheBright, ...newRGB]
245
+ } else {
246
+ hexValues = [0x33, 0x1B, 0x00]
247
+ }
248
+
249
+ // Send the request to the platform sender function
250
+ await this.platform.sendDeviceUpdate(this.accessory, {
251
+ cmd: 'ptReal',
252
+ value: generateCodeFromHexValues(hexValues),
253
+ })
254
+
255
+ // Cache the new state and log if appropriate
256
+ if (this.cacheLightState !== newValue) {
257
+ this.cacheLightState = newValue
258
+ this.accessory.log(`${platformLang.curLight} [${newValue}]`)
259
+ }
260
+ } catch (err) {
261
+ // Catch any errors during the process
262
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
263
+
264
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
265
+ setTimeout(() => {
266
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState === 'on')
267
+ }, 2000)
268
+ throw new this.hapErr(-70402)
269
+ }
270
+ }
271
+
272
+ async internalBrightnessUpdate(value) {
273
+ try {
274
+ // This acts like a debounce function when endlessly sliding the brightness scale
275
+ const updateKeyBright = generateRandomString(5)
276
+ this.updateKeyBright = updateKeyBright
277
+ await sleep(350)
278
+ if (updateKeyBright !== this.updateKeyBright) {
279
+ return
280
+ }
281
+
282
+ // Don't continue if the new value is the same as before
283
+ if (value === this.cacheBright || value === 0) {
284
+ return
285
+ }
286
+
287
+ // Generate the hex values for the code
288
+ const newRGB = hs2rgb(
289
+ this.lightService.getCharacteristic(this.hapChar.Hue).value,
290
+ this.lightService.getCharacteristic(this.hapChar.Saturation).value,
291
+ )
292
+ const hexValues = [0x33, 0x1B, 0x01, value, ...newRGB]
293
+
294
+ // Send the request to the platform sender function
295
+ await this.platform.sendDeviceUpdate(this.accessory, {
296
+ cmd: 'ptReal',
297
+ value: generateCodeFromHexValues(hexValues),
298
+ })
299
+
300
+ // Govee considers 0% brightness to be off
301
+ if (value === 0) {
302
+ setTimeout(() => {
303
+ this.cacheLightState = 'off'
304
+ if (this.lightService.getCharacteristic(this.hapChar.On).value) {
305
+ this.lightService.updateCharacteristic(this.hapChar.On, false)
306
+ this.accessory.log(`${platformLang.curLight} [${this.cacheLightState}]`)
307
+ }
308
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
309
+ }, 1500)
310
+ return
311
+ }
312
+
313
+ // Cache the new state and log if appropriate
314
+ if (this.cacheBright !== value) {
315
+ this.cacheBright = value
316
+ this.accessory.log(`${platformLang.curBright} [${value}%]`)
317
+ }
318
+ } catch (err) {
319
+ // Catch any errors during the process
320
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
321
+
322
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
323
+ setTimeout(() => {
324
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
325
+ }, 2000)
326
+ throw new this.hapErr(-70402)
327
+ }
328
+ }
329
+
330
+ async internalColourUpdate(value) {
331
+ try {
332
+ // This acts like a debounce function when endlessly sliding the colour wheel
333
+ const updateKeyColour = generateRandomString(5)
334
+ this.updateKeyColour = updateKeyColour
335
+ await sleep(300)
336
+ if (updateKeyColour !== this.updateKeyColour) {
337
+ return
338
+ }
339
+
340
+ // Don't continue if the new value is the same as before
341
+ if (value === this.cacheHue) {
342
+ return
343
+ }
344
+
345
+ // Generate the hex values for the code
346
+ const newRGB = hs2rgb(
347
+ value,
348
+ this.lightService.getCharacteristic(this.hapChar.Saturation).value,
349
+ )
350
+ const hexValues = [0x33, 0x1B, 0x01, this.cacheBright, ...newRGB]
351
+
352
+ // Send the request to the platform sender function
353
+ await this.platform.sendDeviceUpdate(this.accessory, {
354
+ cmd: 'ptReal',
355
+ value: generateCodeFromHexValues(hexValues),
356
+ })
357
+
358
+ // Cache the new state and log if appropriate
359
+ if (this.cacheHue !== value) {
360
+ this.cacheHue = value
361
+ this.accessory.log(`${platformLang.curColour} [rgb ${newRGB.join(' ')}]`)
362
+ }
363
+ } catch (err) {
364
+ // Catch any errors during the process
365
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
366
+
367
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
368
+ setTimeout(() => {
369
+ this.lightService.updateCharacteristic(this.hapChar.Hue, this.cacheHue)
370
+ }, 2000)
371
+ throw new this.hapErr(-70402)
372
+ }
373
+ }
374
+
375
+ externalUpdate(params) {
376
+ // Check for an ON/OFF change
377
+ if (params.state && params.state !== this.cacheState) {
378
+ this.cacheState = params.state
379
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
380
+
381
+ // Log the change
382
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
383
+ }
384
+
385
+ // Check for some other scene/mode change
386
+ (params.commands || []).forEach((command) => {
387
+ const hexString = base64ToHex(command)
388
+ const hexParts = hexToTwoItems(hexString)
389
+
390
+ // Return now if not a device query update code
391
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
392
+ return
393
+ }
394
+
395
+ const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
396
+
397
+ switch (deviceFunction) {
398
+ case '0500': { // mode
399
+ // Mode
400
+ const newModeRaw = getTwoItemPosition(hexParts, 4)
401
+ let newMode
402
+ switch (newModeRaw) {
403
+ case '01': {
404
+ // Manual
405
+ newMode = 'manual'
406
+ break
407
+ }
408
+ case '02': {
409
+ // Custom
410
+ newMode = 'custom'
411
+ break
412
+ }
413
+ case '03': {
414
+ // Auto
415
+ newMode = 'auto'
416
+ break
417
+ }
418
+ default:
419
+ return
420
+ }
421
+ if (this.cacheMode !== newMode) {
422
+ this.cacheMode = newMode
423
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
424
+ }
425
+ break
426
+ }
427
+ case '0501': {
428
+ // Manual speed
429
+ const newSpeedRaw = getTwoItemPosition(hexParts, 4)
430
+ if (newSpeedRaw !== this.cacheSpeedRaw) {
431
+ this.cacheSpeedRaw = newSpeedRaw
432
+ this.cacheSpeed = Number.parseInt(newSpeedRaw, 10) * 10
433
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
434
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
435
+ }
436
+ break
437
+ }
438
+ case '1001': { // sometimes humidity value
439
+ const humiCheck = getTwoItemPosition(hexParts, 4)
440
+ if (humiCheck === '00') {
441
+ const newHumiHex = `${getTwoItemPosition(hexParts, 5)}${getTwoItemPosition(hexParts, 6)}`
442
+ const newHumiDec = Math.round(hexToDecimal(newHumiHex)) / 10 // eg 55.5%
443
+ const newHumiHKValue = Math.round(newHumiDec) // eg 56%
444
+ if (newHumiHKValue !== this.cacheHumi) {
445
+ this.cacheHumi = newHumiHKValue
446
+ this.humiService.updateCharacteristic(this.hapChar.CurrentRelativeHumidity, this.cacheHumi)
447
+ this.accessory.log(`${platformLang.curHumi} [${this.cacheHumi}%]`)
448
+ }
449
+ }
450
+ break
451
+ }
452
+ case '1800': // display off
453
+ case '1801': { // display on
454
+ const newDisplay = deviceFunction === '1801' ? 'on' : 'off'
455
+ if (newDisplay !== this.cacheDisplay) {
456
+ this.cacheDisplay = newDisplay
457
+ this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay ? 'on' : 'off'}]`)
458
+ }
459
+ break
460
+ }
461
+ case '1a00': // uv light off
462
+ case '1a01': { // uv light on
463
+ // to implement
464
+ const newUV = deviceFunction === '1a01' ? 'on' : 'off'
465
+ if (newUV !== this.cacheUV) {
466
+ this.cacheUV = newUV
467
+ this.accessory.log(`current uv light [${this.cacheUV}]`)
468
+ }
469
+ break
470
+ }
471
+ case '1b00': // night light off
472
+ case '1b01': { // night light on
473
+ const newNight = deviceFunction === '1b01' ? 'on' : 'off'
474
+ if (newNight !== this.cacheLightState) {
475
+ this.cacheLightState = newNight
476
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState === 'on')
477
+ this.accessory.log(`current night light state [${this.cacheLightState}]`)
478
+ }
479
+
480
+ // Brightness and colour
481
+ if (this.cacheLightState === 'on') {
482
+ const newBrightHex = getTwoItemPosition(hexParts, 4)
483
+ const newBrightDec = Math.round(hexToDecimal(newBrightHex))
484
+ if (newBrightDec !== this.cacheBright) {
485
+ this.cacheBright = newBrightDec
486
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
487
+ this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
488
+ }
489
+
490
+ const newR = hexToDecimal(getTwoItemPosition(hexParts, 5))
491
+ const newG = hexToDecimal(getTwoItemPosition(hexParts, 6))
492
+ const newB = hexToDecimal(getTwoItemPosition(hexParts, 7))
493
+ const hs = rgb2hs(newR, newG, newB)
494
+
495
+ // Check for a colour change
496
+ if (hs[0] !== this.cacheHue) {
497
+ // Colour is different so update Homebridge with new values
498
+ this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
499
+ this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
500
+ [this.cacheHue] = hs
501
+
502
+ // Log the change
503
+ this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
504
+ }
505
+ }
506
+ break
507
+ }
508
+ case '0502': // custom mode
509
+ case '0503': // auto mode
510
+ case '1100': // timer
511
+ case '1101': // timer
512
+ case '1300': // scheduling
513
+ case '1500': { // scheduling
514
+ break
515
+ }
516
+ default:
517
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
518
+ break
519
+ }
520
+ })
521
+ }
522
+ }
@@ -0,0 +1,157 @@
1
+ import {
2
+ base64ToHex,
3
+ getTwoItemPosition,
4
+ hexToTwoItems,
5
+ parseError,
6
+ } from '../utils/functions.js'
7
+ import platformLang from '../utils/lang-en.js'
8
+
9
+ export default class {
10
+ constructor(platform, accessory) {
11
+ // Set up variables from the platform
12
+ this.hapChar = platform.api.hap.Characteristic
13
+ this.hapErr = platform.api.hap.HapStatusError
14
+ this.hapServ = platform.api.hap.Service
15
+ this.platform = platform
16
+
17
+ // Set up variables from the accessory
18
+ this.accessory = accessory
19
+
20
+ // Rotation speed to value in {1, 2, ..., 8}
21
+ this.speed2Value = speed => Math.min(Math.max(Number.parseInt(Math.round(speed / 10), 10), 1), 8)
22
+
23
+ // Speed codes
24
+ this.value2Code = {
25
+ 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
26
+ 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
27
+ 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
28
+ 4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
29
+ 5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
30
+ 6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
31
+ 7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
32
+ 8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
33
+ }
34
+
35
+ // Add the fan service if it doesn't already exist
36
+ this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
37
+
38
+ // Add the set handler to the fan on/off characteristic
39
+ this.service
40
+ .getCharacteristic(this.hapChar.On)
41
+ .onSet(async value => this.internalStateUpdate(value))
42
+ this.cacheState = this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
43
+
44
+ // Add the set handler to the fan rotation speed characteristic
45
+ this.service
46
+ .getCharacteristic(this.hapChar.RotationSpeed)
47
+ .setProps({
48
+ minStep: 10,
49
+ validValues: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
50
+ })
51
+ .onSet(async value => this.internalSpeedUpdate(value))
52
+ this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
53
+
54
+ // Output the customised options to the log
55
+ const opts = JSON.stringify({})
56
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
57
+ }
58
+
59
+ async internalStateUpdate(value) {
60
+ try {
61
+ const newValue = value ? 'on' : 'off'
62
+
63
+ // Don't continue if the new value is the same as before
64
+ if (this.cacheState === newValue) {
65
+ return
66
+ }
67
+
68
+ // Send the request to the platform sender function
69
+ await this.platform.sendDeviceUpdate(this.accessory, {
70
+ cmd: 'stateHumi',
71
+ value: value ? 1 : 0,
72
+ })
73
+
74
+ // Cache the new state and log if appropriate
75
+ this.cacheState = newValue
76
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
77
+ } catch (err) {
78
+ // Catch any errors during the process
79
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
80
+
81
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
82
+ setTimeout(() => {
83
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
84
+ }, 2000)
85
+ throw new this.hapErr(-70402)
86
+ }
87
+ }
88
+
89
+ async internalSpeedUpdate(value) {
90
+ try {
91
+ // Don't continue if the speed is 0
92
+ if (value === 0) {
93
+ return
94
+ }
95
+
96
+ // Get the single Govee value {1, 2, ..., 8}
97
+ const newValue = this.speed2Value(value)
98
+
99
+ // Don't continue if the speed value won't have effect
100
+ if (newValue * 10 === this.cacheSpeed) {
101
+ return
102
+ }
103
+
104
+ // Get the scene code for this value
105
+ const newCode = this.value2Code[newValue]
106
+
107
+ // Send the request to the platform sender function
108
+ await this.platform.sendDeviceUpdate(this.accessory, {
109
+ cmd: 'ptReal',
110
+ value: newCode,
111
+ })
112
+
113
+ // Cache the new state and log if appropriate
114
+ this.cacheSpeed = newValue * 10
115
+ this.accessory.log(`${platformLang.curSpeed} [${newValue}]`)
116
+ } catch (err) {
117
+ // Catch any errors during the process
118
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
119
+
120
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
121
+ setTimeout(() => {
122
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
123
+ }, 2000)
124
+ throw new this.hapErr(-70402)
125
+ }
126
+ }
127
+
128
+ externalUpdate(params) {
129
+ // Check for an ON/OFF change
130
+ if (params.state && params.state !== this.cacheState) {
131
+ this.cacheState = params.state
132
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
133
+
134
+ // Log the change
135
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
136
+ }
137
+
138
+ // Check for some other scene/mode change
139
+ (params.commands || []).forEach((command) => {
140
+ const hexString = base64ToHex(command)
141
+ const hexParts = hexToTwoItems(hexString)
142
+
143
+ // Return now if not a device query update code
144
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
145
+ return
146
+ }
147
+
148
+ const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
149
+
150
+ switch (deviceFunction) {
151
+ default:
152
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
153
+ break
154
+ }
155
+ })
156
+ }
157
+ }