@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,411 @@
1
+ import {
2
+ base64ToHex,
3
+ getTwoItemPosition,
4
+ hexToBase64,
5
+ hexToTwoItems,
6
+ parseError,
7
+ statusToActionCode,
8
+ } from '../utils/functions.js'
9
+ import platformLang from '../utils/lang-en.js'
10
+
11
+ /*
12
+ H7123
13
+ {
14
+ "mode": {
15
+ "options": [
16
+ {
17
+ "name": "Low",
18
+ "value": 1
19
+ },
20
+ {
21
+ "name": "Medium",
22
+ "value": 2
23
+ },
24
+ {
25
+ "name": "High",
26
+ "value": 3
27
+ },
28
+ {
29
+ "name": "Auto mode",
30
+ "value": 4
31
+ },
32
+ {
33
+ "name": "Sleep mode",
34
+ "value": 5
35
+ }
36
+ ]
37
+ }
38
+ }
39
+ */
40
+
41
+ export default class {
42
+ constructor(platform, accessory) {
43
+ // Set up variables from the platform
44
+ this.cusChar = platform.cusChar
45
+ this.hapChar = platform.api.hap.Characteristic
46
+ this.hapErr = platform.api.hap.HapStatusError
47
+ this.hapServ = platform.api.hap.Service
48
+ this.platform = platform
49
+
50
+ // Set up variables from the accessory
51
+ this.accessory = accessory
52
+
53
+ // Speed codes
54
+ this.value2Code = {
55
+ 1: 'OgUFAAAAAAAAAAAAAAAAAAAAADo=', // sleep
56
+ 2: 'OgUBAQAAAAAAAAAAAAAAAAAAAD8=', // low
57
+ 3: 'OgUBAgAAAAAAAAAAAAAAAAAAADw=', // med
58
+ 4: 'OgUBAwAAAAAAAAAAAAAAAAAAAD0=', // high
59
+ 5: 'OgUDAAAAAAAAAAAAAAAAAAAAADw=', // auto
60
+ }
61
+
62
+ this.value2Label = {
63
+ 0: 'off',
64
+ 1: 'sleep',
65
+ 2: 'low',
66
+ 3: 'medium',
67
+ 4: 'high',
68
+ 5: 'auto',
69
+ }
70
+
71
+ this.airValue2Label = {
72
+ 1: 'excellent',
73
+ 2: 'good',
74
+ 3: 'moderate',
75
+ 4: 'poor',
76
+ }
77
+
78
+ // Add the purifier service if it doesn't already exist
79
+ this.service = this.accessory.getService(this.hapServ.AirPurifier)
80
+ || this.accessory.addService(this.hapServ.AirPurifier)
81
+
82
+ // Add the air quality service if it doesn't already exist
83
+ this.airService = this.accessory.getService(this.hapServ.AirQualitySensor)
84
+ || this.accessory.addService(this.hapServ.AirQualitySensor)
85
+
86
+ if (this.airService.testCharacteristic(this.hapChar.PM2_5Density)) {
87
+ this.airService.removeCharacteristic(this.airService.getCharacteristic(this.hapChar.PM2_5Density))
88
+ }
89
+
90
+ this.cacheAir = this.airService.getCharacteristic(this.hapChar.AirQuality).value
91
+
92
+ // Add the set handler to the switch on/off characteristic
93
+ this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
94
+ await this.internalStateUpdate(value)
95
+ })
96
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
97
+
98
+ // Add options to the purifier target state characteristic
99
+ this.service
100
+ .getCharacteristic(this.hapChar.TargetAirPurifierState)
101
+ .updateValue(1)
102
+ .setProps({
103
+ minValue: 1,
104
+ maxValue: 1,
105
+ validValues: [1],
106
+ })
107
+
108
+ // Add the set handler to the fan rotation speed characteristic
109
+ this.service
110
+ .getCharacteristic(this.hapChar.RotationSpeed)
111
+ .setProps({
112
+ minStep: 20,
113
+ validValues: [0, 20, 40, 60, 80, 100],
114
+ })
115
+ .onSet(async value => this.internalSpeedUpdate(value))
116
+ this.cacheMode = this.service.getCharacteristic(this.hapChar.RotationSpeed).value / 20
117
+
118
+ // Add the set handler to the lock controls characteristic
119
+ this.service.getCharacteristic(this.hapChar.LockPhysicalControls).onSet(async (value) => {
120
+ await this.internalLockUpdate(value)
121
+ })
122
+ this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 0 ? 'off' : 'on'
123
+
124
+ // Add night light Eve characteristic if it doesn't exist already
125
+ if (!this.service.testCharacteristic(this.cusChar.NightLight)) {
126
+ this.service.addCharacteristic(this.cusChar.NightLight)
127
+ }
128
+
129
+ // Add display light Eve characteristic if it doesn't exist already
130
+ if (!this.service.testCharacteristic(this.cusChar.DisplayLight)) {
131
+ this.service.addCharacteristic(this.cusChar.DisplayLight)
132
+ }
133
+
134
+ // Add the set handler to the custom display light characteristic
135
+ this.service.getCharacteristic(this.cusChar.DisplayLight).onSet(async (value) => {
136
+ await this.internalDisplayLightUpdate(value)
137
+ })
138
+ this.cacheDisplay = this.service.getCharacteristic(this.cusChar.DisplayLight).value
139
+ ? 'on'
140
+ : 'off'
141
+
142
+ // Output the customised options to the log
143
+ const opts = JSON.stringify({})
144
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
145
+ }
146
+
147
+ async internalStateUpdate(value) {
148
+ try {
149
+ const newValue = value === 1 ? 'on' : 'off'
150
+
151
+ // Don't continue if the new value is the same as before
152
+ if (this.cacheState === newValue) {
153
+ return
154
+ }
155
+
156
+ // Send the request to the platform sender function
157
+ await this.platform.sendDeviceUpdate(this.accessory, {
158
+ cmd: 'statePuri',
159
+ value: value ? 1 : 0,
160
+ })
161
+
162
+ // Update the current state characteristic
163
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
164
+
165
+ // Cache the new state and log if appropriate
166
+ this.cacheState = newValue
167
+ this.accessory.log(`${platformLang.curState} [${newValue}]`)
168
+ } catch (err) {
169
+ // Catch any errors during the process
170
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
171
+
172
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
173
+ setTimeout(() => {
174
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
175
+ }, 2000)
176
+ throw new this.hapErr(-70402)
177
+ }
178
+ }
179
+
180
+ async internalSpeedUpdate(value) {
181
+ try {
182
+ // Don't continue if the speed is 0
183
+ if (value === 0) {
184
+ return
185
+ }
186
+
187
+ // Get the single Govee value {1, 2, 3, 4}
188
+ const newValueKey = value / 20
189
+
190
+ // Don't continue if the speed value won't have effect
191
+ if (!newValueKey || newValueKey === this.cacheMode) {
192
+ return
193
+ }
194
+
195
+ // Send the request to the platform sender function
196
+ await this.platform.sendDeviceUpdate(this.accessory, {
197
+ cmd: 'ptReal',
198
+ value: this.value2Code[newValueKey],
199
+ })
200
+
201
+ // Cache the new state and log if appropriate
202
+ this.cacheMode = newValueKey
203
+ this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
204
+ } catch (err) {
205
+ // Catch any errors during the process
206
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
207
+
208
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
209
+ setTimeout(() => {
210
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
211
+ }, 2000)
212
+ throw new this.hapErr(-70402)
213
+ }
214
+ }
215
+
216
+ async internalLockUpdate(value) {
217
+ try {
218
+ const newValue = value === 1 ? 'on' : 'off'
219
+
220
+ // Don't continue if the new value is the same as before
221
+ if (this.cacheLock === newValue) {
222
+ return
223
+ }
224
+
225
+ // Send the request to the platform sender function
226
+ await this.platform.sendDeviceUpdate(this.accessory, {
227
+ cmd: 'ptReal',
228
+ value: value ? 'MxABAAAAAAAAAAAAAAAAAAAAACI=' : 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
229
+ })
230
+
231
+ // Cache the new state and log if appropriate
232
+ this.cacheLock = newValue
233
+ this.accessory.log(`${platformLang.curLock} [${newValue}]`)
234
+ } catch (err) {
235
+ // Catch any errors during the process
236
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
237
+
238
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
239
+ setTimeout(() => {
240
+ this.service.updateCharacteristic(
241
+ this.hapChar.LockPhysicalControls,
242
+ this.cacheLock === 'on' ? 1 : 0,
243
+ )
244
+ }, 2000)
245
+ throw new this.hapErr(-70402)
246
+ }
247
+ }
248
+
249
+ async internalDisplayLightUpdate(value) {
250
+ try {
251
+ const newValue = value ? 'on' : 'off'
252
+
253
+ // Don't continue if the new value is the same as before
254
+ if (this.cacheDisplay === newValue) {
255
+ return
256
+ }
257
+
258
+ // Generate the code to send
259
+ let codeToSend
260
+ if (value) {
261
+ codeToSend = this.accessory.context.cacheDisplayCode
262
+ ? hexToBase64(statusToActionCode(this.accessory.context.cacheDisplayCode))
263
+ : 'MxYBAAAAAAAAAAAAAAAAAAAAACQ='
264
+ } else {
265
+ codeToSend = 'MxYAAAAAAAAAAAAAAAAAAAAAACU='
266
+ }
267
+
268
+ // Send the request to the platform sender function
269
+ await this.platform.sendDeviceUpdate(this.accessory, {
270
+ cmd: 'ptReal',
271
+ value: codeToSend,
272
+ })
273
+
274
+ // Cache the new state and log if appropriate
275
+ this.cacheDisplay = newValue
276
+ this.accessory.log(`${platformLang.curDisplay} [${newValue}]`)
277
+ } catch (err) {
278
+ // Catch any errors during the process
279
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
280
+
281
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
282
+ setTimeout(() => {
283
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
284
+ }, 2000)
285
+ throw new this.hapErr(-70402)
286
+ }
287
+ }
288
+
289
+ externalUpdate(params) {
290
+ // Check for an ON/OFF change
291
+ if (params.state && params.state !== this.cacheState) {
292
+ this.cacheState = params.state
293
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
294
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, this.cacheState === 'on' ? 2 : 0)
295
+
296
+ // Log the change
297
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
298
+ }
299
+
300
+ (params.commands || []).forEach((command) => {
301
+ const hexString = base64ToHex(command)
302
+ const hexParts = hexToTwoItems(hexString)
303
+
304
+ const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
305
+
306
+ switch (deviceFunction) {
307
+ case 'aa05': // speed
308
+ case '3a05': { // speed
309
+ const newSpeedCode = `${getTwoItemPosition(hexParts, 3)}${getTwoItemPosition(hexParts, 4)}`
310
+
311
+ // Different behaviour for custom speed
312
+ if (newSpeedCode === '0202') {
313
+ this.accessory.log(`${platformLang.curMode} [custom]`)
314
+ return
315
+ }
316
+
317
+ let newMode
318
+
319
+ switch (newSpeedCode) {
320
+ case '0500': {
321
+ // Sleep
322
+ newMode = 1
323
+ break
324
+ }
325
+ case '0101': {
326
+ // Low
327
+ newMode = 2
328
+ break
329
+ }
330
+ case '0102': {
331
+ // Medium
332
+ newMode = 3
333
+ break
334
+ }
335
+ case '0103': {
336
+ // High
337
+ newMode = 4
338
+ break
339
+ }
340
+ case '0300': {
341
+ // Auto
342
+ newMode = 5
343
+ break
344
+ }
345
+ }
346
+
347
+ if (newMode && newMode !== this.cacheMode) {
348
+ this.cacheMode = newMode
349
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
350
+ this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
351
+ }
352
+ break
353
+ }
354
+ case 'aa10': { // lock
355
+ const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
356
+ if (newLock !== this.cacheLock) {
357
+ this.cacheLock = newLock
358
+ this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
359
+ this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
360
+ }
361
+ break
362
+ }
363
+ case 'aa16': { // display light
364
+ const newDisplay = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
365
+ if (newDisplay === 'on') {
366
+ this.accessory.context.cacheDisplayCode = hexString
367
+ }
368
+ if (newDisplay !== this.cacheDisplay) {
369
+ this.cacheDisplay = newDisplay
370
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
371
+
372
+ // Log the change
373
+ this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
374
+ }
375
+ break
376
+ }
377
+ case 'aa19': {
378
+ // Check air quality reading (i.e. 1=green, 2=blue, 3=yellow, 4=red)
379
+ // Cache will be in {1, 2, 3, 5} which relates to Govee {1, 2, 3, 4}
380
+ let newQual = Number.parseInt(getTwoItemPosition(hexParts, 5), 10)
381
+ if (newQual === 4) {
382
+ newQual = 5
383
+ }
384
+
385
+ if (newQual !== this.cacheAir) {
386
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, newQual)
387
+ this.accessory.log(`${platformLang.curAirQual} [${this.airValue2Label[newQual]}]`)
388
+ }
389
+ break
390
+ }
391
+ case 'aa11': // timer
392
+ case 'aa13': // scheduling
393
+ case '3310': // lock (same command for on and off)
394
+ case '3311': // timer
395
+ case '3313': // scheduling
396
+ case '3316': { // display light
397
+ break
398
+ }
399
+ default:
400
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
401
+ break
402
+ }
403
+ })
404
+ }
405
+ }
406
+
407
+ // GOOD
408
+ // [28/12/2022, 00:44:48] [Govee] [Pet Air Purifier] new scene code: [qhkAKQJdAAAAAAAAAAAAAAAAAMU=] [aa 19 00 29 02 5d 00000000000000000000000000c5].
409
+
410
+ // EXC
411
+ // [28/12/2022, 01:12:05] [Govee] [Pet Air Purifier] new scene code: [qhkAHQFdAAAAAAAAAAAAAAAAAPI=] [aa 19 00 1d 01 5d 00000000000000000000000000f2].
@@ -0,0 +1,296 @@
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.cusChar = platform.cusChar
13
+ this.hapChar = platform.api.hap.Characteristic
14
+ this.hapErr = platform.api.hap.HapStatusError
15
+ this.hapServ = platform.api.hap.Service
16
+ this.platform = platform
17
+
18
+ // Set up variables from the accessory
19
+ this.accessory = accessory
20
+
21
+ // Rotation speed to value in {1, 2, 3}
22
+ this.speed2Value = speed => Math.min(Math.max(Number.parseInt(speed / 33, 10), 1), 3)
23
+
24
+ // Speed codes
25
+ this.value2Code = {
26
+ 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=', // sleep
27
+ 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=', // low
28
+ 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=', // high
29
+ }
30
+
31
+ // Lock codes
32
+ this.lock2Code = {
33
+ on: 'MxABAAAAAAAAAAAAAAAAAAAAACI=',
34
+ off: 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
35
+ }
36
+
37
+ // Display codes
38
+ this.display2Code = {
39
+ on: 'MxYBAAAAAAAAAAAAAAAAAAAAACQ=',
40
+ off: 'MxYAAAAAAAAAAAAAAAAAAAAAACU=',
41
+ }
42
+
43
+ // Add the purifier service if it doesn't already exist
44
+ this.service = this.accessory.getService(this.hapServ.AirPurifier)
45
+ || this.accessory.addService(this.hapServ.AirPurifier)
46
+
47
+ // Add the set handler to the switch on/off characteristic
48
+ this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
49
+ await this.internalStateUpdate(value)
50
+ })
51
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
52
+
53
+ // Add options to the purifier target state characteristic
54
+ this.service
55
+ .getCharacteristic(this.hapChar.TargetAirPurifierState)
56
+ .updateValue(1)
57
+ // .setProps({
58
+ // minValue: 1,
59
+ // maxValue: 1,
60
+ // validValues: [1],
61
+ // })
62
+
63
+ // Add the set handler to the fan rotation speed characteristic
64
+ this.service
65
+ .getCharacteristic(this.hapChar.RotationSpeed)
66
+ .setProps({
67
+ minStep: 25,
68
+ validValues: [0, 33, 66, 99],
69
+ })
70
+ .onSet(async value => this.internalSpeedUpdate(value))
71
+ this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
72
+
73
+ // Add the set handler to the lock controls characteristic
74
+ this.service.getCharacteristic(this.hapChar.LockPhysicalControls).onSet(async (value) => {
75
+ await this.internalLockUpdate(value)
76
+ })
77
+ this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 1 ? 'on' : 'off'
78
+
79
+ // Add display light Eve characteristic if it doesn't exist already
80
+ if (!this.service.testCharacteristic(this.cusChar.DisplayLight)) {
81
+ this.service.addCharacteristic(this.cusChar.DisplayLight)
82
+ }
83
+
84
+ // Add the set handler to the custom display light characteristic
85
+ this.service.getCharacteristic(this.cusChar.DisplayLight).onSet(async (value) => {
86
+ await this.internalDisplayLightUpdate(value)
87
+ })
88
+ this.cacheDisplay = this.service.getCharacteristic(this.cusChar.DisplayLight).value
89
+ ? 'on'
90
+ : 'off'
91
+
92
+ // Output the customised options to the log
93
+ const opts = JSON.stringify({})
94
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
95
+ }
96
+
97
+ async internalStateUpdate(value) {
98
+ try {
99
+ const newValue = value === 1 ? 'on' : 'off'
100
+
101
+ // Don't continue if the new value is the same as before
102
+ if (this.cacheState === newValue) {
103
+ return
104
+ }
105
+
106
+ // Send the request to the platform sender function
107
+ await this.platform.sendDeviceUpdate(this.accessory, {
108
+ cmd: 'statePuri',
109
+ value: value ? 1 : 0,
110
+ })
111
+
112
+ // Update the current state characteristic
113
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
114
+
115
+ // Cache the new state and log if appropriate
116
+ this.cacheState = newValue
117
+ this.accessory.log(`${platformLang.curState} [${newValue}]`)
118
+ } catch (err) {
119
+ // Catch any errors during the process
120
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
121
+
122
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
123
+ setTimeout(() => {
124
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
125
+ }, 2000)
126
+ throw new this.hapErr(-70402)
127
+ }
128
+ }
129
+
130
+ async internalSpeedUpdate(value) {
131
+ try {
132
+ // Don't continue if the speed is 0
133
+ if (value === 0) {
134
+ return
135
+ }
136
+
137
+ // Get the single Govee value {1, 2, ..., 8}
138
+ const newValue = this.speed2Value(value)
139
+
140
+ // Don't continue if the speed value won't have effect
141
+ if (newValue * 33 === this.cacheSpeed) {
142
+ return
143
+ }
144
+
145
+ // Get the scene code for this value
146
+ const newCode = this.value2Code[newValue]
147
+
148
+ // Send the request to the platform sender function
149
+ await this.platform.sendDeviceUpdate(this.accessory, {
150
+ cmd: 'ptReal',
151
+ value: newCode,
152
+ })
153
+
154
+ // Cache the new state and log if appropriate
155
+ this.cacheSpeed = newValue * 33
156
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
157
+ } catch (err) {
158
+ // Catch any errors during the process
159
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
160
+
161
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
162
+ setTimeout(() => {
163
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
164
+ }, 2000)
165
+ throw new this.hapErr(-70402)
166
+ }
167
+ }
168
+
169
+ async internalLockUpdate(value) {
170
+ try {
171
+ const newValue = value === 1 ? 'on' : 'off'
172
+
173
+ // Don't continue if the new value is the same as before
174
+ if (this.cacheLock === newValue) {
175
+ return
176
+ }
177
+
178
+ // Send the request to the platform sender function
179
+ await this.platform.sendDeviceUpdate(this.accessory, {
180
+ cmd: 'ptReal',
181
+ value: this.lock2Code[newValue],
182
+ })
183
+
184
+ // Cache the new state and log if appropriate
185
+ this.cacheLock = newValue
186
+ this.accessory.log(`${platformLang.curLock} [${newValue}]`)
187
+ } catch (err) {
188
+ // Catch any errors during the process
189
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
190
+
191
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
192
+ setTimeout(() => {
193
+ this.service.updateCharacteristic(
194
+ this.hapChar.LockPhysicalControls,
195
+ this.cacheLock === 'on' ? 1 : 0,
196
+ )
197
+ }, 2000)
198
+ throw new this.hapErr(-70402)
199
+ }
200
+ }
201
+
202
+ async internalDisplayLightUpdate(value) {
203
+ try {
204
+ const newValue = value ? 'on' : 'off'
205
+
206
+ // Don't continue if the new value is the same as before
207
+ if (this.cacheDisplay === newValue) {
208
+ return
209
+ }
210
+
211
+ // Send the request to the platform sender function
212
+ await this.platform.sendDeviceUpdate(this.accessory, {
213
+ cmd: 'ptReal',
214
+ value: this.display2Code[newValue],
215
+ })
216
+
217
+ // Cache the new state and log if appropriate
218
+ this.cacheDisplay = newValue
219
+ this.accessory.log(`${platformLang.curDisplay} [${newValue}]`)
220
+ } catch (err) {
221
+ // Catch any errors during the process
222
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
223
+
224
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
225
+ setTimeout(() => {
226
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheLight === 'on')
227
+ }, 2000)
228
+ throw new this.hapErr(-70402)
229
+ }
230
+ }
231
+
232
+ externalUpdate(params) {
233
+ // Check for an ON/OFF change
234
+ if (params.state && params.state !== this.cacheState) {
235
+ this.cacheState = params.state
236
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
237
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, this.cacheState === 'on' ? 2 : 0)
238
+
239
+ // Log the change
240
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
241
+ }
242
+
243
+ // Check for some other scene/mode change
244
+ (params.commands || []).forEach((command) => {
245
+ const hexString = base64ToHex(command)
246
+ const hexParts = hexToTwoItems(hexString)
247
+
248
+ // Return now if not a device query update code
249
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
250
+ return
251
+ }
252
+
253
+ const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
254
+
255
+ switch (deviceFunction) {
256
+ case '0501': {
257
+ // Manual speed
258
+ const newSpeedRaw = getTwoItemPosition(hexParts, 4)
259
+ if (newSpeedRaw !== this.cacheSpeedRaw) {
260
+ this.cacheSpeedRaw = newSpeedRaw
261
+ this.cacheSpeed = Number.parseInt(newSpeedRaw, 10) * 10
262
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
263
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
264
+ }
265
+ break
266
+ }
267
+ case 'aa10': { // lock
268
+ const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
269
+ if (newLock !== this.cacheLock) {
270
+ this.cacheLock = newLock
271
+ this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
272
+ this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
273
+ }
274
+ break
275
+ }
276
+ case 'aa16': { // display light
277
+ const newDisplay = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
278
+ if (newDisplay === 'on') {
279
+ this.accessory.context.cacheDisplayCode = hexString
280
+ }
281
+ if (newDisplay !== this.cacheDisplay) {
282
+ this.cacheDisplay = newDisplay
283
+ this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
284
+
285
+ // Log the change
286
+ this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
287
+ }
288
+ break
289
+ }
290
+ default:
291
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
292
+ break
293
+ }
294
+ })
295
+ }
296
+ }