@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,616 @@
1
+ import {
2
+ base64ToHex,
3
+ farToCen,
4
+ getTwoItemPosition,
5
+ hasProperty,
6
+ hexToTwoItems,
7
+ nearestHalf,
8
+ parseError,
9
+ } from '../utils/functions.js'
10
+ import platformLang from '../utils/lang-en.js'
11
+
12
+ /*
13
+ H7130 (with temperature reporting)
14
+ {
15
+ "mode": {
16
+ "options": [
17
+ {
18
+ "name": "Low",
19
+ "value": "1"
20
+ },
21
+ {
22
+ "name": "Medium",
23
+ "value": "2"
24
+ },
25
+ {
26
+ "name": "High",
27
+ "value": "3"
28
+ }
29
+ ]
30
+ }
31
+ }
32
+ */
33
+ export default class {
34
+ constructor(platform, accessory) {
35
+ // Set up variables from the platform
36
+ this.hapChar = platform.api.hap.Characteristic
37
+ this.hapErr = platform.api.hap.HapStatusError
38
+ this.hapServ = platform.api.hap.Service
39
+ this.platform = platform
40
+
41
+ this.log = platform.log
42
+
43
+ // Set up variables from the accessory
44
+ this.accessory = accessory
45
+
46
+ // Set up objects
47
+ this.speedCode = {
48
+ 33: 'MwUBAAAAAAAAAAAAAAAAAAAAADc=',
49
+ 66: 'MwUCAAAAAAAAAAAAAAAAAAAAADQ=',
50
+ 99: 'MwUDAAAAAAAAAAAAAAAAAAAAADU=',
51
+ }
52
+
53
+ this.speedCodeLabel = {
54
+ 33: 'low',
55
+ 66: 'medium',
56
+ 99: 'high',
57
+ }
58
+
59
+ this.tempCodeAuto = {
60
+ 5: 'MxoBAJAEAAAAAAAAAAAAAAAAALw=',
61
+ 6: 'MxoBAJBoAAAAAAAAAAAAAAAAANA=',
62
+ 7: 'MxoBAJEwAAAAAAAAAAAAAAAAAIk=',
63
+ 8: 'MxoBAJH4AAAAAAAAAAAAAAAAAEE=',
64
+ 9: 'MxoBAJLAAAAAAAAAAAAAAAAAAHo=',
65
+ 10: 'MxoBAJOIAAAAAAAAAAAAAAAAADM=',
66
+ 11: 'MxoBAJPsAAAAAAAAAAAAAAAAAFc=',
67
+ 12: 'MxoBAJS0AAAAAAAAAAAAAAAAAAg=',
68
+ 13: 'MxoBAJV8AAAAAAAAAAAAAAAAAME=',
69
+ 14: 'MxoBAJZEAAAAAAAAAAAAAAAAAPo=',
70
+ 15: 'MxoBAJcMAAAAAAAAAAAAAAAAALM=',
71
+ 16: 'MxoBAJdwAAAAAAAAAAAAAAAAAM8=',
72
+ 17: 'MxoBAJg4AAAAAAAAAAAAAAAAAIg=',
73
+ 18: 'MxoBAJkAAAAAAAAAAAAAAAAAALE=',
74
+ 19: 'MxoBAJnIAAAAAAAAAAAAAAAAAHk=',
75
+ 20: 'MxoBAJqQAAAAAAAAAAAAAAAAACI=',
76
+ 21: 'MxoBAJr0AAAAAAAAAAAAAAAAAEY=',
77
+ 22: 'MxoBAJu8AAAAAAAAAAAAAAAAAA8=',
78
+ 23: 'MxoBAJyEAAAAAAAAAAAAAAAAADA=',
79
+ 24: 'MxoBAJ1MAAAAAAAAAAAAAAAAAPk=',
80
+ 25: 'MxoBAJ4UAAAAAAAAAAAAAAAAAKI=',
81
+ 26: 'MxoBAJ54AAAAAAAAAAAAAAAAAM4=',
82
+ 27: 'MxoBAJ9AAAAAAAAAAAAAAAAAAPc=',
83
+ 28: 'MxoBAKAIAAAAAAAAAAAAAAAAAIA=',
84
+ 29: 'MxoBAKDQAAAAAAAAAAAAAAAAAFg=',
85
+ 30: 'MxoBAKGYAAAAAAAAAAAAAAAAABE=',
86
+ }
87
+
88
+ this.tempCodeHeat = {
89
+ 5: 'MxoAAJAEAAAAAAAAAAAAAAAAAL0=',
90
+ 6: 'MxoAAJBoAAAAAAAAAAAAAAAAANE=',
91
+ 7: 'MxoAAJEwAAAAAAAAAAAAAAAAAIg=',
92
+ 8: 'MxoAAJH4AAAAAAAAAAAAAAAAAEA=',
93
+ 9: 'MxoAAJLAAAAAAAAAAAAAAAAAAHs=',
94
+ 10: 'MxoAAJOIAAAAAAAAAAAAAAAAADI=',
95
+ 11: 'MxoAAJPsAAAAAAAAAAAAAAAAAFY=',
96
+ 12: 'MxoAAJS0AAAAAAAAAAAAAAAAAAk=',
97
+ 13: 'MxoAAJV8AAAAAAAAAAAAAAAAAMA=',
98
+ 14: 'MxoAAJZEAAAAAAAAAAAAAAAAAPs=',
99
+ 15: 'MxoAAJcMAAAAAAAAAAAAAAAAALI=',
100
+ 16: 'MxoAAJdwAAAAAAAAAAAAAAAAAM4=',
101
+ 17: 'MxoAAJg4AAAAAAAAAAAAAAAAAIk=',
102
+ 18: 'MxoAAJkAAAAAAAAAAAAAAAAAALA=',
103
+ 19: 'MxoAAJnIAAAAAAAAAAAAAAAAAHg=',
104
+ 20: 'MxoAAJqQAAAAAAAAAAAAAAAAACM=',
105
+ 21: 'MxoAAJr0AAAAAAAAAAAAAAAAAEc=',
106
+ 22: 'MxoAAJu8AAAAAAAAAAAAAAAAAA4=',
107
+ 23: 'MxoAAJyEAAAAAAAAAAAAAAAAADE=',
108
+ 24: 'MxoAAJ1MAAAAAAAAAAAAAAAAAPg=',
109
+ 25: 'MxoAAJ4UAAAAAAAAAAAAAAAAAKM=',
110
+ 26: 'MxoAAJ54AAAAAAAAAAAAAAAAAM8=',
111
+ 27: 'MxoAAJ9AAAAAAAAAAAAAAAAAAPY=',
112
+ 28: 'MxoAAKAIAAAAAAAAAAAAAAAAAIE=',
113
+ 29: 'MxoAAKDQAAAAAAAAAAAAAAAAAFk=',
114
+ 30: 'MxoAAKGYAAAAAAAAAAAAAAAAABA=',
115
+ }
116
+
117
+ // Remove any old light service
118
+ if (this.accessory.getService(this.hapServ.Lightbulb)) {
119
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Lightbulb))
120
+ }
121
+
122
+ // Remove any old fanv2 service
123
+ if (this.accessory.getService(this.hapServ.Fanv2)) {
124
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Fanv2))
125
+ }
126
+
127
+ // Add the heater service if it doesn't already exist
128
+ this.service = this.accessory.getService(this.hapServ.HeaterCooler)
129
+ if (!this.service) {
130
+ this.service = this.accessory.addService(this.hapServ.HeaterCooler)
131
+ this.service.updateCharacteristic(this.hapChar.CurrentTemperature, 20)
132
+ this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, 20)
133
+ }
134
+
135
+ // Add the fan service if it doesn't already exist
136
+ this.fanService = this.accessory.getService(this.hapServ.Fan)
137
+ || this.accessory.addService(this.hapServ.Fan)
138
+
139
+ // Add the set handler to the heater active characteristic
140
+ this.service
141
+ .getCharacteristic(this.hapChar.Active)
142
+ .onSet(async value => this.internalStateUpdate(value))
143
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
144
+
145
+ // Add options to the heater target state characteristic
146
+ this.service
147
+ .getCharacteristic(this.hapChar.TargetHeaterCoolerState)
148
+ .setProps({
149
+ minValue: 0,
150
+ maxValue: 1,
151
+ validValues: [0, 1],
152
+ })
153
+ .onSet(async value => this.internalModeUpdate(value))
154
+ this.cacheMode = this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).value === 0 ? 'auto' : 'heat'
155
+
156
+ this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
157
+
158
+ // Add the set handler and a range to the heater target temperature characteristic
159
+ this.service
160
+ .getCharacteristic(this.hapChar.HeatingThresholdTemperature)
161
+ .setProps({
162
+ minValue: 5,
163
+ maxValue: 30,
164
+ minStep: 1,
165
+ })
166
+ .onSet(async value => this.internalTempUpdate(value))
167
+ this.cacheTarg = this.service.getCharacteristic(this.hapChar.HeatingThresholdTemperature).value
168
+
169
+ // Add the set handler to the heater swing mode characteristic (for oscillation)
170
+ this.service
171
+ .getCharacteristic(this.hapChar.SwingMode)
172
+ .onSet(async value => this.internalSwingUpdate(value))
173
+ this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
174
+
175
+ // Add the set handler to the heater lock characteristic
176
+ this.service
177
+ .getCharacteristic(this.hapChar.LockPhysicalControls)
178
+ .onSet(async value => this.internalLockUpdate(value))
179
+ this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 1 ? 'on' : 'off'
180
+
181
+ // Add the set handler to the fan on/off characteristic
182
+ this.fanService
183
+ .getCharacteristic(this.hapChar.On)
184
+ .onSet(async value => this.internalFanStateUpdate(value))
185
+ this.cacheFanState = this.fanService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
186
+
187
+ // Add the set handler to the fan rotation speed characteristic
188
+ this.fanService
189
+ .getCharacteristic(this.hapChar.RotationSpeed)
190
+ .setProps({
191
+ minStep: 33,
192
+ validValues: [0, 33, 66, 99],
193
+ })
194
+ .onSet(async value => this.internalSpeedUpdate(value))
195
+ this.cacheSpeed = this.fanService.getCharacteristic(this.hapChar.RotationSpeed).value
196
+
197
+ // Output the customised options to the log
198
+ const opts = JSON.stringify({
199
+ tempReporting: true,
200
+ })
201
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
202
+ }
203
+
204
+ async internalStateUpdate(value) {
205
+ try {
206
+ const newValue = value === 1 ? 'on' : 'off'
207
+
208
+ // Don't continue if the new value is the same as before
209
+ if (this.cacheState === newValue) {
210
+ return
211
+ }
212
+
213
+ // Send the request to the platform sender function
214
+ await this.platform.sendDeviceUpdate(this.accessory, {
215
+ cmd: 'ptReal',
216
+ value: value ? 'MwEBAAAAAAAAAAAAAAAAAAAAADM=' : 'MwEAAAAAAAAAAAAAAAAAAAAAADI=',
217
+ })
218
+
219
+ // Cache the new state and log if appropriate
220
+ if (this.cacheState !== newValue) {
221
+ this.cacheState = newValue
222
+ this.accessory.log(`${platformLang.curState} [${newValue}]`)
223
+ }
224
+
225
+ // Fan state should also match the new state
226
+ if (this.cacheFanState !== newValue) {
227
+ this.cacheFanState = newValue
228
+ this.fanService.updateCharacteristic(this.hapChar.On, newValue === 'on')
229
+ }
230
+ } catch (err) {
231
+ // Catch any errors during the process
232
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
233
+
234
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
235
+ setTimeout(() => {
236
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
237
+ }, 2000)
238
+ throw new this.hapErr(-70402)
239
+ }
240
+ }
241
+
242
+ async internalModeUpdate(value) {
243
+ try {
244
+ const newMode = value === 0 ? 'auto' : 'heat'
245
+
246
+ // Don't continue if the new value is the same as before
247
+ if (this.cacheMode === newMode) {
248
+ return
249
+ }
250
+
251
+ // Get the current state of the heater
252
+ const objectToChoose = newMode === 'auto' ? this.tempCodeAuto : this.tempCodeHeat
253
+
254
+ // Send the request to the platform sender function
255
+ await this.platform.sendDeviceUpdate(this.accessory, {
256
+ cmd: 'ptReal',
257
+ value: objectToChoose[this.cacheTemp],
258
+ })
259
+
260
+ // Cache the new state and log if appropriate
261
+ if (this.cacheMode !== newMode) {
262
+ this.cacheMode = newMode
263
+ this.accessory.log(`${platformLang.curMode} [${newMode}]`)
264
+ }
265
+ } catch (err) {
266
+ // Catch any errors during the process
267
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
268
+
269
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
270
+ setTimeout(() => {
271
+ this.service.updateCharacteristic(
272
+ this.hapChar.TargetHeaterCoolerState,
273
+ this.cacheMode === 'auto' ? 0 : 1,
274
+ )
275
+ }, 2000)
276
+ throw new this.hapErr(-70402)
277
+ }
278
+ }
279
+
280
+ async internalTempUpdate(value) {
281
+ try {
282
+ // Don't continue if the new value is the same as before
283
+ if (this.cacheTarg === value) {
284
+ return
285
+ }
286
+
287
+ // Get the current state of the heater
288
+ const objectToChoose = this.cacheMode === 'auto' ? this.tempCodeAuto : this.tempCodeHeat
289
+
290
+ // Send the request to the platform sender function
291
+ await this.platform.sendDeviceUpdate(this.accessory, {
292
+ cmd: 'ptReal',
293
+ value: objectToChoose[value],
294
+ })
295
+
296
+ // Cache the new state and log if appropriate
297
+ if (this.cacheTarg !== value) {
298
+ this.cacheTarg = value
299
+ this.accessory.log(`${platformLang.curTarg} [${this.cacheTarg}°C]`)
300
+ }
301
+ } catch (err) {
302
+ // Catch any errors during the process
303
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
304
+
305
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
306
+ setTimeout(() => {
307
+ this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, this.cacheTarg)
308
+ }, 2000)
309
+ throw new this.hapErr(-70402)
310
+ }
311
+ }
312
+
313
+ async internalSwingUpdate(value) {
314
+ try {
315
+ // value === 0 -> swing mode OFF
316
+ // value === 1 -> swing mode ON
317
+ const newValue = value === 1 ? 'on' : 'off'
318
+
319
+ // Don't continue if the new value is the same as before
320
+ if (this.cacheSwing === newValue) {
321
+ return
322
+ }
323
+
324
+ // Send the request to the platform sender function
325
+ await this.platform.sendDeviceUpdate(this.accessory, {
326
+ cmd: 'ptReal',
327
+ value: value ? 'MxgBAAAAAAAAAAAAAAAAAAAAACo=' : 'MxgAAAAAAAAAAAAAAAAAAAAAACs=',
328
+ })
329
+
330
+ // Cache the new state and log if appropriate
331
+ if (this.cacheSwing !== newValue) {
332
+ this.cacheSwing = newValue
333
+ this.accessory.log(`${platformLang.curSwing} [${newValue}]`)
334
+ }
335
+ } catch (err) {
336
+ // Catch any errors during the process
337
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
338
+
339
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
340
+ setTimeout(() => {
341
+ this.service.updateCharacteristic(
342
+ this.hapChar.SwingMode,
343
+ this.cacheSwing === 'on' ? 1 : 0,
344
+ )
345
+ }, 2000)
346
+ throw new this.hapErr(-70402)
347
+ }
348
+ }
349
+
350
+ async internalLockUpdate(value) {
351
+ try {
352
+ // value === 0 -> child lock OFF
353
+ // value === 1 -> child lock ON
354
+ const newValue = value === 1 ? 'on' : 'off'
355
+
356
+ // Don't continue if the new value is the same as before
357
+ if (this.cacheLock === newValue) {
358
+ return
359
+ }
360
+
361
+ // Send the request to the platform sender function
362
+ await this.platform.sendDeviceUpdate(this.accessory, {
363
+ cmd: 'ptReal',
364
+ value: value ? 'MxABAAAAAAAAAAAAAAAAAAAAACI=' : 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
365
+ })
366
+
367
+ // Cache the new state and log if appropriate
368
+ if (this.cacheLock !== newValue) {
369
+ this.cacheLock = newValue
370
+ this.accessory.log(`${platformLang.curLock} [${newValue}]`)
371
+ }
372
+ } catch (err) {
373
+ // Catch any errors during the process
374
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
375
+
376
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
377
+ setTimeout(() => {
378
+ this.service.updateCharacteristic(
379
+ this.hapChar.LockPhysicalControls,
380
+ this.cacheLock === 'on' ? 1 : 0,
381
+ )
382
+ }, 2000)
383
+ throw new this.hapErr(-70402)
384
+ }
385
+ }
386
+
387
+ async internalFanStateUpdate(value) {
388
+ try {
389
+ const newValue = value ? 'on' : 'off'
390
+
391
+ // The fan is used for the following modes (basically all except Auto):
392
+ // - 0%_: No effect - revert to previous speed
393
+ // - 33%: Low Mode
394
+ // - 66%: Medium Mode
395
+ // - 99%: High Mode
396
+ // If the main heater is turned off then this fan should be turned off too
397
+ // If the main heater is turned on then this fan speed should revert to the current mode
398
+
399
+ // Don't continue if the new value is the same as before
400
+ if (this.cacheFanState === newValue) {
401
+ return
402
+ }
403
+
404
+ // Turning the fan on should only be possible if the main heater is off, and this should not do anything
405
+ if (newValue === 'on') {
406
+ // Wait a few seconds then turn back off
407
+ setTimeout(() => {
408
+ this.fanService.updateCharacteristic(this.hapChar.On, false)
409
+ }, 3000)
410
+ return
411
+ }
412
+
413
+ // Turning fan off:
414
+ // We should wait a few seconds and then just revert to the previous fan speed
415
+ setTimeout(() => {
416
+ this.fanService.updateCharacteristic(this.hapChar.On, true)
417
+ this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
418
+ }, 2000)
419
+ } catch (err) {
420
+ // Catch any errors during the process
421
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
422
+
423
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
424
+ setTimeout(() => {
425
+ this.fanService.updateCharacteristic(this.hapChar.On, this.cacheFanState === 'on')
426
+ }, 2000)
427
+ throw new this.hapErr(-70402)
428
+ }
429
+ }
430
+
431
+ async internalSpeedUpdate(value) {
432
+ try {
433
+ // The fan is used for the following modes (basically all except Auto):
434
+ // - 0%_: Not sure what to do with this yet
435
+ // - 33%: Low Mode
436
+ // - 66%: Medium Mode
437
+ // - 99%: High Mode
438
+ // If the main heater is turned off then this fan should be turned off too
439
+ // If the main heater is turned on then this fan speed should revert to the current mode
440
+
441
+ // Don't continue if the new value is the same as before
442
+ // If the new speed is 0, the on/off handler should take care of resetting to the speed before (home app only)
443
+ if (this.cacheSpeed === value || value === 0) {
444
+ return
445
+ }
446
+
447
+ // Send the request to the platform sender function
448
+ await this.platform.sendDeviceUpdate(this.accessory, {
449
+ cmd: 'ptReal',
450
+ value: this.speedCode[value],
451
+ })
452
+
453
+ // Cache the new state and log if appropriate
454
+ if (this.cacheSpeed !== value) {
455
+ this.cacheSpeed = value
456
+ this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[value]}]`)
457
+ }
458
+ } catch (err) {
459
+ // Catch any errors during the process
460
+ this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
461
+
462
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
463
+ setTimeout(() => {
464
+ this.fanService.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
465
+ }, 2000)
466
+ throw new this.hapErr(-70402)
467
+ }
468
+ }
469
+
470
+ externalUpdate(params) {
471
+ // Update the active characteristic
472
+ if (params.state && params.state !== this.cacheState) {
473
+ this.cacheState = params.state
474
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
475
+ this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
476
+
477
+ // Fan state should also match the main heater state
478
+ if (this.cacheFanState !== this.cacheState) {
479
+ this.cacheFanState = this.cacheState
480
+ this.fanService.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
481
+ }
482
+ }
483
+
484
+ // Update the current temperature characteristic
485
+ if (hasProperty(params, 'temperature')) {
486
+ const newTemp = nearestHalf(farToCen(params.temperature / 100))
487
+ if (newTemp !== this.cacheTemp) {
488
+ if (newTemp > 100) {
489
+ // Device must be one that does not support ambient temperature
490
+ this.accessory.logWarn('you should disable `tempReporting` in the config for this device')
491
+ } else {
492
+ this.cacheTemp = newTemp
493
+ this.service.updateCharacteristic(this.hapChar.CurrentTemperature, this.cacheTemp)
494
+ this.accessory.log(`${platformLang.curTemp} [${this.cacheTemp}°C]`)
495
+ }
496
+ }
497
+ }
498
+
499
+ // Update the target temperature characteristic
500
+ if (hasProperty(params, 'setTemperature')) {
501
+ const newTemp = Math.round(farToCen(params.setTemperature / 100))
502
+ if (newTemp !== this.cacheTarg) {
503
+ this.cacheTarg = newTemp
504
+ this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, this.cacheTarg)
505
+ this.accessory.log(`${platformLang.curTarg} [${this.cacheTarg}°C]`)
506
+ }
507
+ }
508
+
509
+ // Check for some other scene/mode change
510
+ (params.commands || []).forEach((command) => {
511
+ const hexString = base64ToHex(command)
512
+ const hexParts = hexToTwoItems(hexString)
513
+
514
+ // Return now if not a device query update code
515
+ if (getTwoItemPosition(hexParts, 1) !== 'aa') {
516
+ return
517
+ }
518
+
519
+ const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
520
+
521
+ switch (deviceFunction) {
522
+ case '1800':
523
+ case '1801': {
524
+ // Swing Mode
525
+ const newSwing = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
526
+ if (this.cacheSwing !== newSwing) {
527
+ this.cacheSwing = newSwing
528
+ this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
529
+ this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
530
+ }
531
+ break
532
+ }
533
+ case '1000':
534
+ case '1001': {
535
+ // Child Lock
536
+ const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
537
+ if (this.cacheLock !== newLock) {
538
+ this.cacheLock = newLock
539
+ this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
540
+ this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
541
+ }
542
+ break
543
+ }
544
+ case '0501': // fan speed low
545
+ case '0502': // fan speed medium
546
+ case '0503': { // fan speed high
547
+ switch (getTwoItemPosition(hexParts, 3)) {
548
+ case '01': {
549
+ // Fan is low
550
+ if (this.cacheState === 'on' && this.cacheFanState !== 'on') {
551
+ this.cacheFanState = 'on'
552
+ this.fanService.updateCharacteristic(this.hapChar.On, true)
553
+ this.accessory.log(`${platformLang.curMode} [${this.cacheFanState}]`)
554
+ }
555
+ if (this.cacheSpeed !== 33) {
556
+ this.cacheSpeed = 33
557
+ this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
558
+ this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
559
+ }
560
+
561
+ break
562
+ }
563
+ case '02': {
564
+ // Fan is medium
565
+ if (this.cacheState === 'on' && this.cacheFanState !== 'on') {
566
+ this.cacheFanState = 'on'
567
+ this.fanService.updateCharacteristic(this.hapChar.On, true)
568
+ this.accessory.log(`${platformLang.curMode} [${this.cacheFanState}]`)
569
+ }
570
+ if (this.cacheSpeed !== 66) {
571
+ this.cacheSpeed = 66
572
+ this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
573
+ this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
574
+ }
575
+ break
576
+ }
577
+ case '03': {
578
+ // Fan is high
579
+ if (this.cacheState === 'on' && this.cacheFanState !== 'on') {
580
+ this.cacheFanState = 'on'
581
+ this.fanService.updateCharacteristic(this.hapChar.On, true)
582
+ this.accessory.log(`${platformLang.curMode} [${this.cacheFanState}]`)
583
+ }
584
+ if (this.cacheSpeed !== 99) {
585
+ this.cacheSpeed = 99
586
+ this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
587
+ this.accessory.log(`${platformLang.curSpeed} [${this.speedCodeLabel[this.cacheSpeed]}]`)
588
+ }
589
+ break
590
+ }
591
+ }
592
+ break
593
+ }
594
+ case '1a00': // Target temperature (thermostat mode off)
595
+ case '1a01': { // Target temperature (thermostat mode on)
596
+ const newMode = getTwoItemPosition(hexParts, 3) === '01' ? 'auto' : 'heat'
597
+ if (this.cacheMode !== newMode) {
598
+ this.cacheMode = newMode
599
+ this.service.updateCharacteristic(this.hapChar.TargetHeaterCoolerState, this.cacheMode === 'auto' ? 0 : 1)
600
+ this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
601
+ }
602
+ break
603
+ }
604
+ case '1100': // timer off?
605
+ case '1101':// timer on
606
+ case '1300': // scheduling
607
+ case '1600': // DND off?
608
+ case '1601': // DND on?
609
+ break
610
+ default:
611
+ this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
612
+ break
613
+ }
614
+ })
615
+ }
616
+ }