@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,445 @@
1
+ import PQueue from 'p-queue'
2
+ import { TimeoutError } from 'p-timeout'
3
+
4
+ import mqttClient from '../connection/mqtt.js'
5
+ import platformConsts from '../utils/constants.js'
6
+ import { hasProperty, parseError } 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
+ this.inUsePowerThreshold = this.accessory.context.options.inUsePowerThreshold
20
+ || platformConsts.defaultValues.inUsePowerThreshold
21
+ this.name = accessory.displayName
22
+ const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
23
+ ? platform.config.cloudRefreshRate
24
+ : platformConsts.defaultValues.cloudRefreshRate
25
+ const localRefreshRate = hasProperty(platform.config, 'refreshRate')
26
+ ? platform.config.refreshRate
27
+ : platformConsts.defaultValues.refreshRate
28
+ this.pollInterval = accessory.context.connection === 'local'
29
+ ? localRefreshRate
30
+ : cloudRefreshRate
31
+ this.temperatureSource = accessory.context.options.temperatureSource;
32
+
33
+ // If the accessory has any old services then remove them
34
+ ['Switch', 'Outlet', 'AirPurifier'].forEach((service) => {
35
+ if (this.accessory.getService(this.hapServ[service])) {
36
+ this.accessory.removeService(this.accessory.getService(this.hapServ[service]))
37
+ }
38
+ })
39
+
40
+ // Set up the accessory with default target temp when added the first time
41
+ if (!hasProperty(this.accessory.context, 'cacheTarget')) {
42
+ this.accessory.context.cacheTarget = 20
43
+ }
44
+
45
+ // Check to make sure user has not switched from cooler to heater
46
+ if (this.accessory.context.cacheType !== 'heater') {
47
+ // Remove and re-setup as a HeaterCooler
48
+ if (this.accessory.getService(this.hapServ.HeaterCooler)) {
49
+ this.accessory.removeService(this.accessory.getService(this.hapServ.HeaterCooler))
50
+ }
51
+ this.accessory.context.cacheType = 'heater'
52
+ this.accessory.context.cacheTarget = 20
53
+ }
54
+
55
+ // Add the heater service if it doesn't already exist
56
+ this.service = this.accessory.getService(this.hapServ.HeaterCooler)
57
+ || this.accessory.addService(this.hapServ.HeaterCooler)
58
+
59
+ // Set custom properties of the current temperature characteristic
60
+ this.service.getCharacteristic(this.hapChar.CurrentTemperature).setProps({
61
+ minStep: 0.1,
62
+ })
63
+ this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
64
+
65
+ // Add the set handler to the heater active characteristic
66
+ this.service
67
+ .getCharacteristic(this.hapChar.Active)
68
+ .onSet(async value => this.internalStateUpdate(value))
69
+
70
+ // Add options to the target state characteristic
71
+ this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).setProps({
72
+ minValue: 0,
73
+ maxValue: 0,
74
+ validValues: [0],
75
+ })
76
+
77
+ // Add the set handler to the target temperature characteristic
78
+ this.service
79
+ .getCharacteristic(this.hapChar.HeatingThresholdTemperature)
80
+ .updateValue(this.accessory.context.cacheTarget)
81
+ .setProps({ minStep: 0.5 })
82
+ .onSet(async value => this.internalTargetTempUpdate(value))
83
+
84
+ // Initialise these caches now since they aren't determined by the initial externalUpdate()
85
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1
86
+ this.cacheHeat = this.cacheState
87
+ && this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).value === 2
88
+
89
+ // Pass the accessory to Fakegato to set up with Eve
90
+ this.accessory.eveService = new platform.eveService('custom', this.accessory, { log: () => {} })
91
+
92
+ // Create the queue used for sending device requests
93
+ this.updateInProgress = false
94
+ this.queue = new PQueue({
95
+ concurrency: 1,
96
+ interval: 250,
97
+ intervalCap: 1,
98
+ timeout: 10000,
99
+ throwOnTimeout: true,
100
+ })
101
+ this.queue.on('idle', () => {
102
+ this.updateInProgress = false
103
+ })
104
+
105
+ // Set up the mqtt client for cloud devices to send and receive device updates
106
+ if (accessory.context.connection !== 'local') {
107
+ this.accessory.mqtt = new mqttClient(platform, this.accessory)
108
+ this.accessory.mqtt.connect()
109
+ }
110
+
111
+ // Always request a device update on startup, then start the interval for polling
112
+ setTimeout(() => this.requestUpdate(true), 2000)
113
+ this.accessory.refreshInterval = setInterval(
114
+ () => this.requestUpdate(),
115
+ this.pollInterval * 1000,
116
+ )
117
+
118
+ // Set up an interval to get regular temperature updates
119
+ setTimeout(() => {
120
+ this.getTemperature()
121
+ this.accessory.powerInterval = setInterval(
122
+ () => this.getTemperature(),
123
+ 120000,
124
+ )
125
+ }, 5000)
126
+
127
+ // Output the customised options to the log
128
+ const opts = JSON.stringify({
129
+ connection: this.accessory.context.connection,
130
+ showAs: 'heater',
131
+ temperatureSource: this.temperatureSource,
132
+ })
133
+ platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
134
+ }
135
+
136
+ async internalStateUpdate(value) {
137
+ try {
138
+ // Add the request to the queue so updates are sent apart
139
+ await this.queue.add(async () => {
140
+ let newState
141
+ let newHeat
142
+ let newValue
143
+ if (value !== 0) {
144
+ newState = true
145
+ if (this.cacheTemp < this.accessory.context.cacheTarget) {
146
+ newValue = true
147
+ newHeat = true
148
+ }
149
+ }
150
+
151
+ // Only send the update if either:
152
+ // * The new value (state) is OFF and the cacheHeat was ON
153
+ // * The new value (state) is ON and newHeat is 'on'
154
+ if ((value === 0 && this.cacheHeat) || (value === 1 && newHeat)) {
155
+ // This flag stops the plugin from requesting updates while pending on others
156
+ this.updateInProgress = true
157
+
158
+ // The plugin should have determined if it's 'toggle' or 'togglex' on the first poll run
159
+ let namespace
160
+ let payload
161
+ if (this.isToggleX) {
162
+ namespace = 'Appliance.Control.ToggleX'
163
+ payload = {
164
+ togglex: {
165
+ onoff: newValue ? 1 : 0,
166
+ channel: 0,
167
+ },
168
+ }
169
+ } else {
170
+ namespace = 'Appliance.Control.Toggle'
171
+ payload = {
172
+ toggle: {
173
+ onoff: newValue ? 1 : 0,
174
+ },
175
+ }
176
+ }
177
+
178
+ // Use the platform function to send the update to the device
179
+ await this.platform.sendUpdate(this.accessory, {
180
+ namespace,
181
+ payload,
182
+ })
183
+ }
184
+ if (newState !== this.cacheState) {
185
+ this.cacheState = newState
186
+ this.accessory.log(`${platformLang.curState} [${this.cacheState ? 'on' : 'off'}]`)
187
+ }
188
+ if (newHeat !== this.cacheHeat) {
189
+ this.cacheHeat = newHeat
190
+ this.accessory.log(`${platformLang.curHeat} [${this.cacheHeat ? 'on' : 'off'}]`)
191
+ }
192
+ const newOnState = this.cacheHeat ? 2 : 1
193
+ this.service.updateCharacteristic(
194
+ this.hapChar.CurrentHeaterCoolerState,
195
+ value === 1 ? newOnState : 0,
196
+ )
197
+ })
198
+ } catch (err) {
199
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
200
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
201
+ setTimeout(() => {
202
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState ? 1 : 0)
203
+ }, 2000)
204
+ throw new this.hapErr(-70402)
205
+ }
206
+ }
207
+
208
+ async internalTargetTempUpdate(value) {
209
+ try {
210
+ // Add the request to the queue so updates are sent apart
211
+ await this.queue.add(async () => {
212
+ if (value === this.accessory.context.cacheTarget) {
213
+ return
214
+ }
215
+ this.accessory.context.cacheTarget = value
216
+ this.accessory.log(`${platformLang.curTarg} [${value}°C]`)
217
+
218
+ if (!this.cacheState) {
219
+ return
220
+ }
221
+ let newHeat
222
+ let newValue
223
+ if (this.cacheTemp < value) {
224
+ newValue = true
225
+ newHeat = true
226
+ }
227
+ if (newHeat === this.cacheHeat) {
228
+ return
229
+ }
230
+ // This flag stops the plugin from requesting updates while pending on others
231
+ this.updateInProgress = true
232
+
233
+ // The plugin should have determined if it's 'toggle' or 'togglex' on the first poll run
234
+ let namespace
235
+ let payload
236
+ if (this.isToggleX) {
237
+ namespace = 'Appliance.Control.ToggleX'
238
+ payload = {
239
+ togglex: {
240
+ onoff: newValue ? 1 : 0,
241
+ channel: 0,
242
+ },
243
+ }
244
+ } else {
245
+ namespace = 'Appliance.Control.Toggle'
246
+ payload = {
247
+ toggle: {
248
+ onoff: newValue ? 1 : 0,
249
+ },
250
+ }
251
+ }
252
+
253
+ // Use the platform function to send the update to the device
254
+ await this.platform.sendUpdate(this.accessory, {
255
+ namespace,
256
+ payload,
257
+ })
258
+
259
+ // Cache and log
260
+ this.cacheHeat = newHeat
261
+ this.accessory.log(`${platformLang.curHeat} [${this.cacheHeat ? 'on' : 'off'}]`)
262
+ this.service.updateCharacteristic(
263
+ this.hapChar.CurrentHeaterCoolerState,
264
+ this.cacheHeat ? 2 : 1,
265
+ )
266
+ })
267
+ } catch (err) {
268
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
269
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
270
+ setTimeout(() => {
271
+ this.service.updateCharacteristic(
272
+ this.hapChar.HeatingThresholdTemperature,
273
+ this.accessory.context.cacheTarget,
274
+ )
275
+ }, 2000)
276
+ throw new this.hapErr(-70402)
277
+ }
278
+ }
279
+
280
+ async internalCurrentTempUpdate() {
281
+ try {
282
+ // Add the request to the queue so updates are sent apart
283
+ await this.queue.add(async () => {
284
+ if (!this.cacheState) {
285
+ return
286
+ }
287
+ let newHeat
288
+ let newValue
289
+ if (this.cacheTemp < this.accessory.context.cacheTarget) {
290
+ newValue = true
291
+ newHeat = true
292
+ }
293
+ if (newHeat === this.cacheHeat) {
294
+ return
295
+ }
296
+ // This flag stops the plugin from requesting updates while pending on others
297
+ this.updateInProgress = true
298
+
299
+ // The plugin should have determined if it's 'toggle' or 'togglex' on the first poll run
300
+ let namespace
301
+ let payload
302
+ if (this.isToggleX) {
303
+ namespace = 'Appliance.Control.ToggleX'
304
+ payload = {
305
+ togglex: {
306
+ onoff: newValue ? 1 : 0,
307
+ channel: 0,
308
+ },
309
+ }
310
+ } else {
311
+ namespace = 'Appliance.Control.Toggle'
312
+ payload = {
313
+ toggle: {
314
+ onoff: newValue ? 1 : 0,
315
+ },
316
+ }
317
+ }
318
+
319
+ // Use the platform function to send the update to the device
320
+ await this.platform.sendUpdate(this.accessory, {
321
+ namespace,
322
+ payload,
323
+ })
324
+
325
+ // Cache and log
326
+ this.cacheHeat = newHeat
327
+ this.accessory.log(`${platformLang.curHeat} [${this.cacheHeat ? 'on' : 'off'}]`)
328
+ this.service.updateCharacteristic(
329
+ this.hapChar.CurrentHeaterCoolerState,
330
+ this.cacheHeat ? 2 : 1,
331
+ )
332
+ })
333
+ } catch (err) {
334
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
335
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
336
+ }
337
+ }
338
+
339
+ async getTemperature() {
340
+ try {
341
+ // Skip polling if the storage hasn't initialised properly
342
+ if (!this.platform.storageClientData) {
343
+ return
344
+ }
345
+
346
+ const newTemp = await this.platform.storageData.getItem(`${this.temperatureSource}_temp`)
347
+ if (newTemp && newTemp !== this.cacheTemp) {
348
+ this.cacheTemp = newTemp
349
+ this.service.updateCharacteristic(this.hapChar.CurrentTemperature, this.cacheTemp)
350
+ this.accessory.eveService.addEntry({ temp: this.cacheTemp })
351
+
352
+ this.accessory.log(`${platformLang.curTemp} [${this.cacheTemp}°C]`)
353
+ await this.internalCurrentTempUpdate()
354
+ }
355
+ } catch (err) {
356
+ this.accessory.logWarn(parseError(err))
357
+ }
358
+ }
359
+
360
+ async requestUpdate(firstRun = false) {
361
+ try {
362
+ // Don't continue if an update is currently being sent to the device
363
+ if (this.updateInProgress) {
364
+ return
365
+ }
366
+
367
+ // Add the request to the queue so updates are sent apart
368
+ await this.queue.add(async () => {
369
+ // This flag stops the plugin from requesting updates while pending on others
370
+ this.updateInProgress = true
371
+
372
+ // Send the request
373
+ const res = await this.platform.sendUpdate(this.accessory, {
374
+ namespace: 'Appliance.System.All',
375
+ payload: {},
376
+ })
377
+
378
+ // Log the received data
379
+ this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
380
+
381
+ // Check the response is in a useful format
382
+ const data = res.data.payload
383
+ if (data.all) {
384
+ if (firstRun && data.all.digest) {
385
+ if (data.all.digest.togglex && data.all.digest.togglex[0]) {
386
+ this.isToggleX = true
387
+ }
388
+ }
389
+
390
+ // A flag to check if we need to update the accessory context
391
+ let needsUpdate = false
392
+
393
+ // Get the mac address and hardware version of the device
394
+ if (data.all.system) {
395
+ // Mac address and hardware don't change regularly so only get on first poll
396
+ if (firstRun && data.all.system.hardware) {
397
+ this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
398
+ this.accessory.context.hardware = data.all.system.hardware.version
399
+ }
400
+
401
+ // Get the ip address and firmware of the device
402
+ if (data.all.system.firmware) {
403
+ // Check for an IP change each and every time the device is polled
404
+ if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
405
+ this.accessory.context.ipAddress = data.all.system.firmware.innerIp
406
+ needsUpdate = true
407
+ }
408
+
409
+ // Firmware doesn't change regularly so only get on first poll
410
+ if (firstRun) {
411
+ this.accessory.context.firmware = data.all.system.firmware.version
412
+ }
413
+ }
414
+ }
415
+
416
+ // Get the cloud online status of the device
417
+ if (data.all.system.online) {
418
+ const isOnline = data.all.system.online.status === 1
419
+ if (this.accessory.context.isOnline !== isOnline) {
420
+ this.accessory.context.isOnline = isOnline
421
+ needsUpdate = true
422
+ }
423
+ }
424
+
425
+ // Update the accessory cache if anything has changed
426
+ if (needsUpdate || firstRun) {
427
+ this.platform.updateAccessory(this.accessory)
428
+ }
429
+ }
430
+ })
431
+ } catch (err) {
432
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
433
+ this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
434
+
435
+ // Set the homebridge-ui status of the device to offline if local and error is timeout
436
+ if (
437
+ (this.accessory.context.isOnline || firstRun)
438
+ && ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
439
+ ) {
440
+ this.accessory.context.isOnline = false
441
+ this.platform.updateAccessory(this.accessory)
442
+ }
443
+ }
444
+ }
445
+ }
@@ -0,0 +1,56 @@
1
+ import platformConsts from '../utils/constants.js'
2
+ import { hasProperty, parseError } from '../utils/functions.js'
3
+ import platformLang from '../utils/lang-en.js'
4
+
5
+ export default class {
6
+ constructor(platform, accessory) {
7
+ // Set up variables from the platform
8
+ this.hapChar = platform.api.hap.Characteristic
9
+ this.hapErr = platform.api.hap.HapStatusError
10
+ this.hapServ = platform.api.hap.Service
11
+ this.platform = platform
12
+
13
+ // Set up variables from the accessory
14
+ this.accessory = accessory
15
+ this.lowBattThreshold = accessory.context.options.lowBattThreshold
16
+ ? Math.min(accessory.context.options.lowBattThreshold, 100)
17
+ : platformConsts.defaultValues.lowBattThreshold
18
+ this.name = accessory.displayName
19
+
20
+ // Add the battery service if it doesn't already exist
21
+ this.battService = this.accessory.getService(this.hapServ.Battery) || this.accessory.addService(this.hapServ.Battery)
22
+ this.cacheBatt = this.battService.getCharacteristic(this.hapChar.BatteryLevel).value
23
+
24
+ // Output the customised options to the log
25
+ const opts = JSON.stringify({
26
+ connection: this.accessory.context.connection,
27
+ lowBattThreshold: this.lowBattThreshold,
28
+ })
29
+ platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
30
+ }
31
+
32
+ applyUpdate(data) {
33
+ try {
34
+ // Battery % from reported voltage
35
+ if (hasProperty(data, 'voltage')) {
36
+ // 1. Reduce/enlarge value so in [2000, 3000]
37
+ let newVoltage = Math.min(Math.max(data.voltage, 2000), 3000)
38
+
39
+ // 2. Scale this from [2000, 3000] to [0, 100] and round to nearest whole number
40
+ newVoltage = Math.round((newVoltage - 2000) / 10)
41
+
42
+ // This should be a rough estimate of the battery %
43
+ if (newVoltage !== this.cacheBatt) {
44
+ this.cacheBatt = newVoltage
45
+ this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
46
+ this.battService.updateCharacteristic(
47
+ this.hapChar.StatusLowBattery,
48
+ this.cacheBatt < this.lowBattThreshold ? 1 : 0,
49
+ )
50
+ }
51
+ }
52
+ } catch (err) {
53
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,86 @@
1
+ import { hasProperty, parseError } from '../utils/functions.js'
2
+ import platformLang from '../utils/lang-en.js'
3
+
4
+ export default class {
5
+ constructor(platform, accessory) {
6
+ // Set up variables from the platform
7
+ this.eveChar = platform.eveChar
8
+ this.hapChar = platform.api.hap.Characteristic
9
+ this.hapErr = platform.api.hap.HapStatusError
10
+ this.hapServ = platform.api.hap.Service
11
+ this.platform = platform
12
+
13
+ // Set up variables from the accessory
14
+ this.accessory = accessory
15
+
16
+ // Battery service is not available with this device, so remove the service if exists
17
+ if (this.accessory.getService(this.hapServ.Battery)) {
18
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Battery))
19
+ }
20
+
21
+ // Add the leak sensor service if it doesn't already exist
22
+ this.service = this.accessory.getService(this.hapServ.LeakSensor)
23
+ if (!this.service) {
24
+ this.service = this.accessory.addService(this.hapServ.LeakSensor)
25
+ this.service.addCharacteristic(this.eveChar.LastActivation)
26
+ }
27
+
28
+ // Pass the accessory to Fakegato to set up with Eve
29
+ this.accessory.eveService = new platform.eveService('motion', this.accessory, { log: () => {} })
30
+
31
+ // Reset to no leak when homebridge starts
32
+ this.service.updateCharacteristic(this.hapChar.LeakDetected, 0)
33
+ this.accessory.eveService.addEntry({ status: 0 })
34
+ this.cacheLeak = 0
35
+
36
+ // Output the customised options to the log
37
+ const opts = JSON.stringify({
38
+ connection: this.accessory.context.connection,
39
+ })
40
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
41
+ }
42
+
43
+ applyUpdate(data) {
44
+ try {
45
+ // data.waterLeak.latestWaterLeak from cloud is 1 or 0
46
+ // data.latestWaterLeak from mqtt is 1 or 0
47
+ let newStatus
48
+
49
+ if (hasProperty(data, 'latestWaterLeak')) {
50
+ newStatus = data.latestWaterLeak
51
+ } else if (hasProperty(data, 'waterLeak') && hasProperty(data.waterLeak, 'latestWaterLeak')) {
52
+ newStatus = data.waterLeak.latestWaterLeak
53
+ }
54
+
55
+ switch (newStatus) {
56
+ case 1: {
57
+ // Leak detected
58
+ if (this.cacheLeak !== 1) {
59
+ this.service.updateCharacteristic(this.hapChar.LeakDetected, 1)
60
+ this.accessory.eveService.addEntry({ status: 1 })
61
+ this.cacheLeak = 1
62
+ this.accessory.log(`${platformLang.curLeak} [yes]`)
63
+ }
64
+ break
65
+ }
66
+ case 0: {
67
+ // No leak detected
68
+ if (this.cacheLeak !== 0) {
69
+ this.service.updateCharacteristic(this.hapChar.LeakDetected, 0)
70
+ this.accessory.eveService.addEntry({ status: 0 })
71
+ this.cacheLeak = 0
72
+ this.accessory.log(`${platformLang.curLeak} [no]`)
73
+ }
74
+ break
75
+ }
76
+ default: {
77
+ // Unknown status if possible
78
+ this.accessory.logWarn(`unknown latestWaterLeak status received: ${JSON.stringify(data)}`)
79
+ break
80
+ }
81
+ }
82
+ } catch (err) {
83
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
84
+ }
85
+ }
86
+ }