@homebridge-plugins/homebridge-meross 10.8.0

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 (54) hide show
  1. package/CHANGELOG.md +1346 -0
  2. package/LICENSE +21 -0
  3. package/README.md +68 -0
  4. package/config.schema.json +2066 -0
  5. package/eslint.config.js +49 -0
  6. package/lib/connection/http.js +345 -0
  7. package/lib/connection/mqtt.js +174 -0
  8. package/lib/device/baby.js +532 -0
  9. package/lib/device/cooler-single.js +447 -0
  10. package/lib/device/diffuser.js +730 -0
  11. package/lib/device/fan.js +530 -0
  12. package/lib/device/garage-main.js +225 -0
  13. package/lib/device/garage-single.js +495 -0
  14. package/lib/device/garage-sub.js +376 -0
  15. package/lib/device/heater-single.js +445 -0
  16. package/lib/device/hub-contact.js +56 -0
  17. package/lib/device/hub-leak.js +86 -0
  18. package/lib/device/hub-main.js +403 -0
  19. package/lib/device/hub-sensor.js +115 -0
  20. package/lib/device/hub-smoke.js +40 -0
  21. package/lib/device/hub-valve.js +377 -0
  22. package/lib/device/humidifier.js +521 -0
  23. package/lib/device/index.js +63 -0
  24. package/lib/device/light-cct.js +474 -0
  25. package/lib/device/light-dimmer.js +312 -0
  26. package/lib/device/light-rgb.js +528 -0
  27. package/lib/device/outlet-multi.js +383 -0
  28. package/lib/device/outlet-single.js +405 -0
  29. package/lib/device/power-strip.js +282 -0
  30. package/lib/device/purifier-single.js +372 -0
  31. package/lib/device/purifier.js +403 -0
  32. package/lib/device/roller-location.js +317 -0
  33. package/lib/device/roller.js +234 -0
  34. package/lib/device/sensor-presence.js +201 -0
  35. package/lib/device/switch-multi.js +403 -0
  36. package/lib/device/switch-single.js +371 -0
  37. package/lib/device/template.js +177 -0
  38. package/lib/device/thermostat.js +493 -0
  39. package/lib/fakegato/LICENSE +21 -0
  40. package/lib/fakegato/fakegato-history.js +814 -0
  41. package/lib/fakegato/fakegato-storage.js +108 -0
  42. package/lib/fakegato/fakegato-timer.js +125 -0
  43. package/lib/fakegato/uuid.js +27 -0
  44. package/lib/homebridge-ui/public/index.html +316 -0
  45. package/lib/homebridge-ui/server.js +10 -0
  46. package/lib/index.js +8 -0
  47. package/lib/platform.js +1256 -0
  48. package/lib/utils/colour.js +581 -0
  49. package/lib/utils/constants.js +377 -0
  50. package/lib/utils/custom-chars.js +165 -0
  51. package/lib/utils/eve-chars.js +130 -0
  52. package/lib/utils/functions.js +39 -0
  53. package/lib/utils/lang-en.js +114 -0
  54. package/package.json +70 -0
@@ -0,0 +1,730 @@
1
+ import PQueue from 'p-queue'
2
+ import { TimeoutError } from 'p-timeout'
3
+
4
+ import mqttClient from '../connection/mqtt.js'
5
+ import {
6
+ hk2mrRGB,
7
+ hs2rgb,
8
+ mr2hkRGB,
9
+ rgb2hs,
10
+ } from '../utils/colour.js'
11
+ import platformConsts from '../utils/constants.js'
12
+ import {
13
+ generateRandomString,
14
+ hasProperty,
15
+ parseError,
16
+ sleep,
17
+ } from '../utils/functions.js'
18
+ import platformLang from '../utils/lang-en.js'
19
+
20
+ export default class {
21
+ constructor(platform, accessory) {
22
+ // Set up variables from the platform
23
+ this.cusChar = platform.cusChar
24
+ this.hapChar = platform.api.hap.Characteristic
25
+ this.hapErr = platform.api.hap.HapStatusError
26
+ this.hapServ = platform.api.hap.Service
27
+ this.platform = platform
28
+
29
+ // Set up variables from the accessory
30
+ this.accessory = accessory
31
+ this.brightnessStep = this.accessory.context.options.brightnessStep || platformConsts.defaultValues.brightnessStep
32
+ this.brightnessStep = Math.min(this.brightnessStep, 100)
33
+ this.name = accessory.displayName
34
+ const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
35
+ ? platform.config.cloudRefreshRate
36
+ : platformConsts.defaultValues.cloudRefreshRate
37
+ const localRefreshRate = hasProperty(platform.config, 'refreshRate')
38
+ ? platform.config.refreshRate
39
+ : platformConsts.defaultValues.refreshRate
40
+ this.pollInterval = accessory.context.connection === 'local'
41
+ ? localRefreshRate
42
+ : cloudRefreshRate
43
+ this.hk2mr = (speed) => {
44
+ if (speed === 0) {
45
+ return 2
46
+ }
47
+ if (speed <= 75) {
48
+ return 0
49
+ }
50
+ return 1
51
+ }
52
+ this.hk2Label = (speed) => {
53
+ if (speed === 0) {
54
+ return 'off'
55
+ }
56
+ if (speed <= 75) {
57
+ return 'mild'
58
+ }
59
+ return 'full'
60
+ }
61
+ this.mr2hk = (speed) => {
62
+ if (speed === 0) {
63
+ return 50
64
+ }
65
+ if (speed === 1) {
66
+ return 100
67
+ }
68
+ return 0
69
+ }
70
+
71
+ // Add the diffuser (fan) service if it doesn't already exist
72
+ this.fanService = this.accessory.getService('Diffuser')
73
+ || this.accessory.addService(this.hapServ.Fan, 'Diffuser', 'diffuser')
74
+
75
+ // Add the lightbulb service if it doesn't already exist
76
+ this.lightService = this.accessory.getService('Light')
77
+ || this.accessory.addService(this.hapServ.Lightbulb, 'Light', 'light')
78
+
79
+ // Add the set handler to the diffuser (fan) on/off service
80
+ this.fanService
81
+ .getCharacteristic(this.hapChar.On)
82
+ .onSet(async value => this.internalFanStateUpdate(value))
83
+ this.cacheFanState = this.fanService.getCharacteristic(this.hapChar.On).value
84
+
85
+ this.fanService
86
+ .getCharacteristic(this.hapChar.RotationSpeed)
87
+ .setProps({
88
+ minStep: 50,
89
+ validValues: [0, 50, 100],
90
+ })
91
+ .onSet(async value => this.internalFanSpeedUpdate(value))
92
+ this.cacheFanSpeed = this.hk2mr(
93
+ this.fanService.getCharacteristic(this.hapChar.RotationSpeed).value,
94
+ )
95
+
96
+ // Add the set handler to the lightbulb on/off characteristic
97
+ this.lightService
98
+ .getCharacteristic(this.hapChar.On)
99
+ .onSet(async value => this.internalLightStateUpdate(value))
100
+ this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value
101
+
102
+ // Add the set handler to the lightbulb brightness
103
+ this.lightService
104
+ .getCharacteristic(this.hapChar.Brightness)
105
+ .setProps({ minStep: this.brightnessStep })
106
+ .onSet(async value => this.internalLightBrightnessUpdate(value))
107
+ this.cacheLightBright = this.lightService.getCharacteristic(this.hapChar.Brightness).value
108
+
109
+ // Add the set handler to the lightbulb hue characteristic
110
+ this.lightService
111
+ .getCharacteristic(this.hapChar.Hue)
112
+ .onSet(async value => this.internalLightColourUpdate(value))
113
+ this.cacheLightHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
114
+ this.cacheLightSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
115
+
116
+ // Add the set handler to the diffuser (fan) custom colour mode characteristic
117
+ if (!this.lightService.testCharacteristic(this.cusChar.DiffColourMode)) {
118
+ this.lightService.addCharacteristic(this.cusChar.DiffColourMode)
119
+ }
120
+ this.lightService
121
+ .getCharacteristic(this.cusChar.DiffColourMode)
122
+ .onSet(async value => this.internalLightModeUpdate(value, 'colour'))
123
+ if (this.lightService.getCharacteristic(this.cusChar.DiffColourMode).value) {
124
+ this.cacheLightMode = 1
125
+ }
126
+
127
+ // Add the set handler to the diffuser (fan) custom rainbow mode characteristic
128
+ if (!this.lightService.testCharacteristic(this.cusChar.DiffRainbowMode)) {
129
+ this.lightService.addCharacteristic(this.cusChar.DiffRainbowMode)
130
+ }
131
+ this.lightService
132
+ .getCharacteristic(this.cusChar.DiffRainbowMode)
133
+ .onSet(async value => this.internalLightModeUpdate(value, 'rainbow'))
134
+ if (this.lightService.getCharacteristic(this.cusChar.DiffRainbowMode).value) {
135
+ this.cacheLightMode = 0
136
+ }
137
+
138
+ // Add the set handler to the diffuser (fan) custom temperature mode characteristic
139
+ if (!this.lightService.testCharacteristic(this.cusChar.DiffTemperatureMode)) {
140
+ this.lightService.addCharacteristic(this.cusChar.DiffTemperatureMode)
141
+ }
142
+ this.lightService
143
+ .getCharacteristic(this.cusChar.DiffTemperatureMode)
144
+ .onSet(async value => this.internalLightModeUpdate(value, 'temperature'))
145
+ if (this.lightService.getCharacteristic(this.cusChar.DiffTemperatureMode).value) {
146
+ this.cacheLightMode = 2
147
+ }
148
+
149
+ // Create the queue used for sending device requests
150
+ this.updateInProgress = false
151
+ this.queue = new PQueue({
152
+ concurrency: 1,
153
+ interval: 250,
154
+ intervalCap: 1,
155
+ timeout: 10000,
156
+ throwOnTimeout: true,
157
+ })
158
+ this.queue.on('idle', () => {
159
+ this.updateInProgress = false
160
+ })
161
+
162
+ // Set up the mqtt client for cloud devices to send and receive device updates
163
+ if (accessory.context.connection !== 'local') {
164
+ this.accessory.mqtt = new mqttClient(platform, this.accessory)
165
+ this.accessory.mqtt.connect()
166
+ }
167
+
168
+ // Always request a device update on startup, then start the interval for polling
169
+ setTimeout(() => this.requestUpdate(true), 2000)
170
+ this.accessory.refreshInterval = setInterval(
171
+ () => this.requestUpdate(),
172
+ this.pollInterval * 1000,
173
+ )
174
+
175
+ // Output the customised options to the log
176
+ const opts = JSON.stringify({
177
+ brightnessStep: this.brightnessStep,
178
+ connection: this.accessory.context.connection,
179
+ })
180
+ platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
181
+ }
182
+
183
+ async internalFanStateUpdate(value) {
184
+ try {
185
+ // Add the request to the queue so updates are sent apart
186
+ await this.queue.add(async () => {
187
+ // Don't continue if the state is the same as before
188
+ if (value === this.cacheFanState) {
189
+ return
190
+ }
191
+
192
+ // This flag stops the plugin from requesting updates while pending on others
193
+ this.updateInProgress = true
194
+
195
+ // Generate the payload and namespace
196
+ const namespace = 'Appliance.Control.Diffuser.Spray'
197
+ const payload = {
198
+ type: 'mod100',
199
+ spray: [
200
+ {
201
+ mode: value ? this.cacheFanSpeed : 2,
202
+ channel: 0,
203
+ },
204
+ ],
205
+ }
206
+
207
+ // Use the platform function to send the update to the device
208
+ await this.platform.sendUpdate(this.accessory, {
209
+ namespace,
210
+ payload,
211
+ })
212
+
213
+ // Update the cache and log the update has been successful
214
+ this.cacheFanState = value
215
+
216
+ this.accessory.log(`${platformLang.curDiffState} [${value ? 'on' : 'off'}]`)
217
+ })
218
+ } catch (err) {
219
+ // Catch any errors whilst updating the device
220
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
221
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
222
+ setTimeout(() => {
223
+ this.fanService.updateCharacteristic(this.hapChar.On, this.cacheFanState)
224
+ }, 2000)
225
+ throw new this.hapErr(-70402)
226
+ }
227
+ }
228
+
229
+ async internalFanSpeedUpdate(value) {
230
+ try {
231
+ // Add the request to the queue so updates are sent apart
232
+ await this.queue.add(async () => {
233
+ // Some homekit apps might not support the valid values of 0, 50 and 100
234
+ if (value === 0) {
235
+ value = 0
236
+ } else if (value <= 75) {
237
+ value = 50
238
+ } else {
239
+ value = 100
240
+ }
241
+
242
+ // Don't continue if the state is the same as before
243
+ const mrVal = this.hk2mr(value)
244
+ if (mrVal === this.cacheFanSpeed) {
245
+ return
246
+ }
247
+
248
+ // This flag stops the plugin from requesting updates while pending on others
249
+ this.updateInProgress = true
250
+
251
+ // Generate the payload and namespace
252
+ const namespace = 'Appliance.Control.Diffuser.Spray'
253
+ const payload = {
254
+ type: 'mod100',
255
+ spray: [
256
+ {
257
+ mode: mrVal,
258
+ channel: 0,
259
+ },
260
+ ],
261
+ }
262
+
263
+ // Use the platform function to send the update to the device
264
+ await this.platform.sendUpdate(this.accessory, {
265
+ namespace,
266
+ payload,
267
+ })
268
+
269
+ // If using the slider to turn off then set the rotation speed back to original value
270
+ // This stops homekit turning back to 100% if using the icon after turned off
271
+ if (value === 0) {
272
+ // Update the rotation speed back to the previous value (with the fan still off)
273
+ setTimeout(() => {
274
+ this.fanService.updateCharacteristic(
275
+ this.hapChar.RotationSpeed,
276
+ this.mr2hk(this.cacheFanSpeed),
277
+ )
278
+ }, 2000)
279
+ } else {
280
+ // Update the cache and log the update has been successful
281
+ this.cacheFanSpeed = mrVal
282
+ this.accessory.log(`${platformLang.curDiffSpray} [${this.hk2Label(value)}]`)
283
+ }
284
+ })
285
+ } catch (err) {
286
+ // Catch any errors whilst updating the device
287
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
288
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
289
+ setTimeout(() => {
290
+ this.fanService.updateCharacteristic(
291
+ this.hapChar.RotationSpeed,
292
+ this.mr2hk(this.cacheFanSpeed),
293
+ )
294
+ }, 2000)
295
+ throw new this.hapErr(-70402)
296
+ }
297
+ }
298
+
299
+ async internalLightStateUpdate(value) {
300
+ try {
301
+ // Add the request to the queue so updates are sent apart
302
+ await this.queue.add(async () => {
303
+ // Don't continue if the state is the same as before
304
+ if (value === this.cacheLightState) {
305
+ return
306
+ }
307
+
308
+ // This flag stops the plugin from requesting updates while pending on others
309
+ this.updateInProgress = true
310
+
311
+ // Generate the payload and namespace
312
+ const namespace = 'Appliance.Control.Diffuser.Light'
313
+ const payload = {
314
+ type: 'mod100',
315
+ light: [
316
+ {
317
+ onoff: value ? 1 : 0,
318
+ channel: 0,
319
+ },
320
+ ],
321
+ }
322
+
323
+ // Use the platform function to send the update to the device
324
+ await this.platform.sendUpdate(this.accessory, {
325
+ namespace,
326
+ payload,
327
+ })
328
+
329
+ // Update the cache and log the update has been successful
330
+ this.cacheLightState = value
331
+
332
+ this.accessory.log(`${platformLang.curLightState} [${value ? 'on' : 'off'}]`)
333
+ })
334
+ } catch (err) {
335
+ // Catch any errors whilst updating the device
336
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
337
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
338
+ setTimeout(() => {
339
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState)
340
+ }, 2000)
341
+ throw new this.hapErr(-70402)
342
+ }
343
+ }
344
+
345
+ async internalLightBrightnessUpdate(value) {
346
+ try {
347
+ // Add the request to the queue so updates are sent apart
348
+ await this.queue.add(async () => {
349
+ // Don't continue if the state is the same as before
350
+ if (this.cacheLightBright === value) {
351
+ return
352
+ }
353
+
354
+ // Avoid multiple changes in short space of time
355
+ const updateKey = generateRandomString(5)
356
+ this.updateKeyBright = updateKey
357
+ await sleep(300)
358
+ if (updateKey !== this.updateKeyBright) {
359
+ return
360
+ }
361
+
362
+ // This flag stops the plugin from requesting updates while pending on others
363
+ this.updateInProgress = true
364
+
365
+ // Generate the payload to send for the correct device model
366
+ const namespace = 'Appliance.Control.Diffuser.Light'
367
+ const payload = {
368
+ type: 'mod100',
369
+ light: [
370
+ {
371
+ luminance: value,
372
+ channel: 0,
373
+ },
374
+ ],
375
+ }
376
+
377
+ // Use the platform function to send the update to the device
378
+ await this.platform.sendUpdate(this.accessory, {
379
+ namespace,
380
+ payload,
381
+ })
382
+
383
+ // Update the cache and log the update has been successful
384
+ this.cacheLightBright = value
385
+ this.accessory.log(`${platformLang.curLightBright} [${value}%]`)
386
+ })
387
+ } catch (err) {
388
+ const eText = parseError(err)
389
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
390
+ setTimeout(() => {
391
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheLightBright)
392
+ }, 2000)
393
+ throw new this.hapErr(-70402)
394
+ }
395
+ }
396
+
397
+ async internalLightColourUpdate(value) {
398
+ try {
399
+ // Add the request to the queue so updates are sent apart
400
+ await this.queue.add(async () => {
401
+ // Don't continue if the state is the same as before
402
+ if (this.cacheLightHue === value) {
403
+ return
404
+ }
405
+
406
+ // Avoid multiple changes in short space of time
407
+ const updateKey = generateRandomString(5)
408
+ this.updateKeyColour = updateKey
409
+ await sleep(300)
410
+ if (updateKey !== this.updateKeyColour) {
411
+ return
412
+ }
413
+
414
+ // This flag stops the plugin from requesting updates while pending on others
415
+ this.updateInProgress = true
416
+
417
+ // Convert to RGB
418
+ const saturation = this.lightService.getCharacteristic(this.hapChar.Saturation).value
419
+ const [r, g, b] = hs2rgb(value, saturation)
420
+
421
+ // Generate the payload to send
422
+ const namespace = 'Appliance.Control.Diffuser.Light'
423
+ const payload = {
424
+ type: 'mod100',
425
+ light: [
426
+ {
427
+ rgb: hk2mrRGB(r, g, b),
428
+ mode: 1,
429
+ channel: 0,
430
+ },
431
+ ],
432
+ }
433
+
434
+ // Use the platform function to send the update to the device
435
+ await this.platform.sendUpdate(this.accessory, {
436
+ namespace,
437
+ payload,
438
+ })
439
+
440
+ // Update the cache and log the update has been successful
441
+ this.cacheLightHue = value
442
+ this.cacheLightSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
443
+ this.accessory.log(`${platformLang.curLightColour} [${this.cacheLightHue}, ${this.cacheLightSat}] / [${r}, ${g}, ${b}]`)
444
+ })
445
+ } catch (err) {
446
+ const eText = parseError(err)
447
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
448
+ setTimeout(() => {
449
+ this.lightService.updateCharacteristic(this.hapChar.Hue, this.cacheLightHue)
450
+ }, 2000)
451
+ throw new this.hapErr(-70402)
452
+ }
453
+ }
454
+
455
+ async internalLightModeUpdate(value, mode) {
456
+ try {
457
+ // Add the request to the queue so updates are sent apart
458
+ await this.queue.add(async () => {
459
+ // Don't continue if we are turning off, can't leave a mode without selecting another
460
+ if (!value) {
461
+ return
462
+ }
463
+
464
+ // This flag stops the plugin from requesting updates while pending on others
465
+ this.updateInProgress = true
466
+
467
+ // Get the mode value from the mode selected
468
+ let modeVal
469
+ switch (mode) {
470
+ case 'colour':
471
+ modeVal = 1
472
+ break
473
+ case 'rainbow':
474
+ modeVal = 0
475
+ break
476
+ case 'temperature':
477
+ modeVal = 2
478
+ break
479
+ default:
480
+ // Should never happen
481
+ return
482
+ }
483
+
484
+ // Generate the payload and namespace
485
+ const namespace = 'Appliance.Control.Diffuser.Light'
486
+ const payload = {
487
+ type: 'mod100',
488
+ light: [
489
+ {
490
+ mode: modeVal,
491
+ channel: 0,
492
+ },
493
+ ],
494
+ }
495
+
496
+ // Use the platform function to send the update to the device
497
+ await this.platform.sendUpdate(this.accessory, {
498
+ namespace,
499
+ payload,
500
+ });
501
+
502
+ // Turn all the mode characteristics OFF, the needed one will turn ON at end of this func
503
+ ['DiffColourMode', 'DiffRainbowMode', 'DiffTemperatureMode'].forEach((cusChar) => {
504
+ this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
505
+ })
506
+
507
+ // Update the cache and log the update has been successful
508
+ this.cacheLightMode = modeVal
509
+ this.accessory.log(`${platformLang.curLightMode} [${mode}]`)
510
+ })
511
+ } catch (err) {
512
+ // Catch any errors whilst updating the device
513
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
514
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
515
+ let charToError
516
+ switch (mode) {
517
+ case 'colour':
518
+ charToError = this.cusChar.DiffColourMode
519
+ break
520
+ case 'rainbow':
521
+ charToError = this.cusChar.DiffRainbowMode
522
+ break
523
+ case 'temperature':
524
+ charToError = this.cusChar.DiffTemperatureMode
525
+ break
526
+ default:
527
+ // Should never happen
528
+ return
529
+ }
530
+ setTimeout(() => {
531
+ this.lightService.updateCharacteristic(charToError, false)
532
+ }, 2000)
533
+ throw new this.hapErr(-70402)
534
+ }
535
+ }
536
+
537
+ async requestUpdate(firstRun = false) {
538
+ try {
539
+ // Don't continue if an update is currently being sent to the device
540
+ if (this.updateInProgress) {
541
+ return
542
+ }
543
+
544
+ // Add the request to the queue so updates are sent apart
545
+ await this.queue.add(async () => {
546
+ // This flag stops the plugin from requesting updates while pending on others
547
+ this.updateInProgress = true
548
+
549
+ // Send the request
550
+ const res = await this.platform.sendUpdate(this.accessory, {
551
+ namespace: 'Appliance.System.All',
552
+ payload: {},
553
+ })
554
+
555
+ // Log the received data
556
+ this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
557
+
558
+ // Check the response is in a useful format
559
+ const data = res.data.payload
560
+
561
+ if (data.all) {
562
+ if (data.all.digest && data.all.digest.diffuser) {
563
+ this.applyUpdate(data.all.digest.diffuser)
564
+ }
565
+
566
+ // A flag to check if we need to update the accessory context
567
+ let needsUpdate = false
568
+
569
+ // Get the mac address and hardware version of the device
570
+ if (data.all.system) {
571
+ // Mac address and hardware don't change regularly so only get on first poll
572
+ if (firstRun && data.all.system.hardware) {
573
+ this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
574
+ this.accessory.context.hardware = data.all.system.hardware.version
575
+ }
576
+
577
+ // Get the ip address and firmware of the device
578
+ if (data.all.system.firmware) {
579
+ // Check for an IP change each and every time the device is polled
580
+ if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
581
+ this.accessory.context.ipAddress = data.all.system.firmware.innerIp
582
+ needsUpdate = true
583
+ }
584
+
585
+ // Firmware doesn't change regularly so only get on first poll
586
+ if (firstRun) {
587
+ this.accessory.context.firmware = data.all.system.firmware.version
588
+ }
589
+ }
590
+ }
591
+
592
+ // Get the cloud online status of the device
593
+ if (data.all.system.online) {
594
+ const isOnline = data.all.system.online.status === 1
595
+ if (this.accessory.context.isOnline !== isOnline) {
596
+ this.accessory.context.isOnline = isOnline
597
+ needsUpdate = true
598
+ }
599
+ }
600
+
601
+ // Update the accessory cache if anything has changed
602
+ if (needsUpdate || firstRun) {
603
+ this.platform.updateAccessory(this.accessory)
604
+ }
605
+ }
606
+ })
607
+ } catch (err) {
608
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
609
+ this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
610
+
611
+ // Set the homebridge-ui status of the device to offline if local and error is timeout
612
+ if (
613
+ (this.accessory.context.isOnline || firstRun)
614
+ && ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
615
+ ) {
616
+ this.accessory.context.isOnline = false
617
+ this.platform.updateAccessory(this.accessory)
618
+ }
619
+ }
620
+ }
621
+
622
+ receiveUpdate(params) {
623
+ try {
624
+ // Log the received data
625
+ this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
626
+
627
+ // Check the response is in a useful format
628
+ const data = params.payload
629
+ if (data.light || data.spray) {
630
+ this.applyUpdate(data)
631
+ }
632
+ } catch (err) {
633
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
634
+ }
635
+ }
636
+
637
+ applyUpdate(data) {
638
+ // Update the diffuser (fan) service from the supplied data
639
+ if (data.spray && data.spray[0] && hasProperty(data.spray[0], 'mode')) {
640
+ const newSpeed = data.spray[0].mode
641
+ const newState = newSpeed !== 2
642
+
643
+ // Check against the cache and update HomeKit and the cache if needed
644
+ if (this.cacheFanState !== newState || this.cacheFanSpeed !== newSpeed) {
645
+ this.cacheFanState = newState
646
+ this.cacheFanSpeed = newSpeed
647
+ if (this.cacheFanState) {
648
+ // Looks like the spray is now on (from OFF or a different mode)
649
+ this.cacheFanState = true
650
+ this.fanService.updateCharacteristic(this.hapChar.On, true)
651
+ this.accessory.log(`${platformLang.curDiffState} [on]`)
652
+ const hkValue = this.mr2hk(this.cacheFanSpeed)
653
+ this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, hkValue)
654
+ this.accessory.log(`${platformLang.curDiffSpray} [${this.hk2Label(hkValue)}]`)
655
+ } else {
656
+ // Looks like the spray has been turned off
657
+ this.fanService.updateCharacteristic(this.hapChar.On, false)
658
+ this.accessory.log(`${platformLang.curDiffState} [off]`)
659
+ }
660
+ }
661
+ }
662
+
663
+ // Update the light from the supplied data
664
+ if (data.light && data.light[0]) {
665
+ if (hasProperty(data.light[0], 'onoff')) {
666
+ const newState = data.light[0].onoff === 1
667
+
668
+ // Check against the cache and update HomeKit and the cache if needed
669
+ if (this.cacheLightState !== newState) {
670
+ this.lightService.updateCharacteristic(this.hapChar.On, newState)
671
+ this.cacheLightState = newState
672
+ this.accessory.log(`${platformLang.curLightState} [${this.cacheLightState ? 'on' : 'off'}]`)
673
+ }
674
+ }
675
+ if (hasProperty(data.light[0], 'luminance')) {
676
+ const newBright = data.light[0].luminance
677
+
678
+ // Check against the cache and update HomeKit and the cache if needed
679
+ if (this.cacheBright !== newBright) {
680
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, newBright)
681
+ this.cacheLightBright = newBright
682
+ this.accessory.log(`${platformLang.curLightBright} [${this.cacheLightBright}%]`)
683
+ }
684
+ }
685
+ if (data.light[0].mode === 0) {
686
+ if (!this.cacheLightMode !== 0) {
687
+ this.cacheLightMode = 0;
688
+ ['DiffColourMode', 'DiffTemperatureMode'].forEach((cusChar) => {
689
+ this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
690
+ })
691
+ this.lightService.updateCharacteristic(this.cusChar.DiffRainbowMode, true)
692
+ this.accessory.log(`${platformLang.curLightMode} [rainbow]`)
693
+ }
694
+ }
695
+ if (data.light[0].mode === 1) {
696
+ if (!this.cacheLightMode !== 1) {
697
+ this.cacheLightMode = 1;
698
+ ['DiffRainbowMode', 'DiffTemperatureMode'].forEach((cusChar) => {
699
+ this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
700
+ })
701
+ this.lightService.updateCharacteristic(this.cusChar.DiffColourMode, true)
702
+ this.accessory.log(`${platformLang.curLightMode} [colour]`)
703
+ }
704
+ if (hasProperty(data.light[0], 'rgb')) {
705
+ const [r, g, b] = mr2hkRGB(data.light[0].rgb)
706
+ const [newHue, newSat] = rgb2hs(r, g, b)
707
+
708
+ // Check against the cache and update HomeKit and the cache if needed
709
+ if (this.cacheLightHue !== newHue || this.cacheLightSat !== newSat) {
710
+ this.lightService.updateCharacteristic(this.hapChar.Hue, newHue)
711
+ this.lightService.updateCharacteristic(this.hapChar.Saturation, newSat)
712
+ this.cacheLightHue = newHue
713
+ this.cacheLightSat = newSat
714
+ this.accessory.log(`${platformLang.curLightColour} [${this.cacheLightHue}, ${this.cacheLightSat}] / [${r}, ${g}, ${b}]`)
715
+ }
716
+ }
717
+ }
718
+ if (data.light[0].mode === 2) {
719
+ if (!this.cacheLightMode !== 2) {
720
+ this.cacheLightMode = 2;
721
+ ['DiffColourMode', 'DiffRainbowMode'].forEach((cusChar) => {
722
+ this.lightService.updateCharacteristic(this.cusChar[cusChar], false)
723
+ })
724
+ this.lightService.updateCharacteristic(this.cusChar.DiffTemperatureMode, true)
725
+ this.accessory.log(`${platformLang.curLightMode} [temperature]`)
726
+ }
727
+ }
728
+ }
729
+ }
730
+ }