@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,201 @@
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.name = accessory.displayName
20
+ const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
21
+ ? platform.config.cloudRefreshRate
22
+ : platformConsts.defaultValues.cloudRefreshRate
23
+ const localRefreshRate = hasProperty(platform.config, 'refreshRate')
24
+ ? platform.config.refreshRate
25
+ : platformConsts.defaultValues.refreshRate
26
+ this.pollInterval = accessory.context.connection === 'local'
27
+ ? localRefreshRate
28
+ : cloudRefreshRate
29
+
30
+ this.value2Label = (value) => {
31
+ switch (value) {
32
+ case 1: return 'absence'
33
+ case 2: return 'presence'
34
+ case 3: return 'minor motion'
35
+ case 4: return 'motion'
36
+ case 5: return 'approach'
37
+ case 6: return 'moving away'
38
+ default: return 'unknown'
39
+ }
40
+ }
41
+
42
+ // Add an occupancy sensor if it doesn't already exist
43
+ this.occupancyService = this.accessory.getService(this.hapServ.OccupancySensor)
44
+ || this.accessory.addService(this.hapServ.OccupancySensor)
45
+ this.cacheOccupancy = this.occupancyService.getCharacteristic(this.hapChar.OccupancyDetected).value
46
+
47
+ // Add the light sensor if it doesn't already exist
48
+ this.lightService = this.accessory.getService(this.hapServ.LightSensor)
49
+ || this.accessory.addService(this.hapServ.LightSensor)
50
+ this.cacheLux = this.lightService.getCharacteristic(this.hapChar.CurrentAmbientLightLevel).value
51
+
52
+ // Create the queue used for sending device requests
53
+ this.updateInProgress = false
54
+ this.queue = new PQueue({
55
+ concurrency: 1,
56
+ interval: 250,
57
+ intervalCap: 1,
58
+ timeout: 10000,
59
+ throwOnTimeout: true,
60
+ })
61
+ this.queue.on('idle', () => {
62
+ this.updateInProgress = false
63
+ })
64
+
65
+ // Set up the mqtt client for cloud devices to send and receive device updates
66
+ if (accessory.context.connection !== 'local') {
67
+ this.accessory.mqtt = new mqttClient(platform, this.accessory)
68
+ this.accessory.mqtt.connect()
69
+ }
70
+
71
+ // Always request a device update on startup, then start the interval for polling
72
+ setTimeout(() => this.requestUpdate(true), 2000)
73
+ this.accessory.refreshInterval = setInterval(
74
+ () => this.requestUpdate(),
75
+ this.pollInterval * 1000,
76
+ )
77
+
78
+ // Output the customised options to the log
79
+ const opts = JSON.stringify({
80
+ connection: this.accessory.context.connection,
81
+ })
82
+ platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
83
+ }
84
+
85
+ async requestUpdate(firstRun = false) {
86
+ try {
87
+ // Don't continue if an update is currently being sent to the device
88
+ if (this.updateInProgress) {
89
+ return
90
+ }
91
+
92
+ // Add the request to the queue so updates are sent apart
93
+ await this.queue.add(async () => {
94
+ // This flag stops the plugin from requesting updates while pending on others
95
+ this.updateInProgress = true
96
+
97
+ // Send the request
98
+ const res = await this.platform.sendUpdate(this.accessory, {
99
+ namespace: 'Appliance.System.All',
100
+ payload: {},
101
+ })
102
+
103
+ // Log the received data
104
+ this.accessory.logWarn(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
105
+
106
+ // Check the response is in a useful format
107
+ const data = res.data.payload
108
+ if (data.all) {
109
+ // A flag to check if we need to update the accessory context
110
+ let needsUpdate = false
111
+
112
+ // Get the mac address and hardware version of the device
113
+ if (data.all.system) {
114
+ // Mac address and hardware don't change regularly so only get on first poll
115
+ if (firstRun && data.all.system.hardware) {
116
+ this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
117
+ this.accessory.context.hardware = data.all.system.hardware.version
118
+ }
119
+
120
+ // Get the ip address and firmware of the device
121
+ if (data.all.system.firmware) {
122
+ // Check for an IP change each and every time the device is polled
123
+ if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
124
+ this.accessory.context.ipAddress = data.all.system.firmware.innerIp
125
+ needsUpdate = true
126
+ }
127
+
128
+ // Firmware doesn't change regularly so only get on first poll
129
+ if (firstRun) {
130
+ this.accessory.context.firmware = data.all.system.firmware.version
131
+ }
132
+ }
133
+ }
134
+
135
+ // Get the cloud online status of the device
136
+ if (data.all.system.online) {
137
+ const isOnline = data.all.system.online.status === 1
138
+ if (this.accessory.context.isOnline !== isOnline) {
139
+ this.accessory.context.isOnline = isOnline
140
+ needsUpdate = true
141
+ }
142
+ }
143
+
144
+ // Update the accessory cache if anything has changed
145
+ if (needsUpdate || firstRun) {
146
+ this.platform.updateAccessory(this.accessory)
147
+ }
148
+ }
149
+ })
150
+ } catch (err) {
151
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
152
+ this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
153
+
154
+ // Set the homebridge-ui status of the device to offline if local and error is timeout
155
+ if (
156
+ (this.accessory.context.isOnline || firstRun)
157
+ && ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
158
+ ) {
159
+ this.accessory.context.isOnline = false
160
+ this.platform.updateAccessory(this.accessory)
161
+ }
162
+ }
163
+ }
164
+
165
+ receiveUpdate(params) {
166
+ try {
167
+ // Log the received data
168
+ this.accessory.logWarn(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
169
+ if (params.payload?.latest?.[0]?.data) {
170
+ this.applyUpdate(params.payload.latest[0].data)
171
+ }
172
+ } catch (err) {
173
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
174
+ }
175
+ }
176
+
177
+ applyUpdate(data) {
178
+ if (data.light?.[0]?.value) {
179
+ // Check against the cache and update HomeKit and the cache if needed
180
+ const newLux = data.light[0].value
181
+ if (this.cacheLux !== newLux) {
182
+ this.lightService.updateCharacteristic(this.hapChar.CurrentAmbientLightLevel, newLux)
183
+ this.cacheLux = newLux
184
+ this.accessory.log(`${platformLang.curLux} [${newLux}]`)
185
+ }
186
+ }
187
+
188
+ if (data.presence?.[0]?.value && data.presence[0].value !== this.cacheOccupancyRaw) {
189
+ this.cacheOccupancyRaw = data.presence[0].value
190
+
191
+ // Check against the cache and update HomeKit and the cache if needed
192
+ const newOccupancy = this.cacheOccupancyRaw !== 1 ? 1 : 0
193
+ if (this.cacheOccupancy !== newOccupancy) {
194
+ this.occupancyService.updateCharacteristic(this.hapChar.OccupancyDetected, newOccupancy)
195
+ this.cacheOccupancy = newOccupancy
196
+ }
197
+
198
+ this.accessory.log(`${platformLang.curOcc} [${this.value2Label(this.cacheOccupancyRaw)}]`)
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,403 @@
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.devicesInHB = platform.devicesInHB
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
+ this.name = accessory.displayName
21
+ const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
22
+ ? platform.config.cloudRefreshRate
23
+ : platformConsts.defaultValues.cloudRefreshRate
24
+ const localRefreshRate = hasProperty(platform.config, 'refreshRate')
25
+ ? platform.config.refreshRate
26
+ : platformConsts.defaultValues.refreshRate
27
+ this.pollInterval = accessory.context.connection === 'local'
28
+ ? localRefreshRate
29
+ : cloudRefreshRate
30
+ this.priAcc = this.devicesInHB.get(
31
+ this.platform.api.hap.uuid.generate(`${accessory.context.serialNumber}0`),
32
+ )
33
+
34
+ // If the accessory has an outlet service then remove it
35
+ if (this.accessory.getService(this.hapServ.Outlet)) {
36
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Outlet))
37
+ }
38
+
39
+ // Add the switch service if it doesn't already exist
40
+ this.service = this.accessory.getService(this.hapServ.Switch)
41
+ || this.accessory.addService(this.hapServ.Switch)
42
+
43
+ // Add the set handler to the switch on/off characteristic
44
+ this.service
45
+ .getCharacteristic(this.hapChar.On)
46
+ .onSet(async value => this.internalStateUpdate(value))
47
+ this.cacheState = this.service.getCharacteristic(this.hapChar.On).value
48
+
49
+ // Pass the accessory to Fakegato to set up with Eve
50
+ this.accessory.eveService = new platform.eveService('switch', this.accessory, { log: () => {} })
51
+
52
+ // Create the queue used for sending device requests
53
+ this.updateInProgress = false
54
+ this.queue = new PQueue({
55
+ concurrency: 1,
56
+ interval: 250,
57
+ intervalCap: 1,
58
+ timeout: 10000,
59
+ throwOnTimeout: true,
60
+ })
61
+ this.queue.on('idle', () => {
62
+ this.updateInProgress = false
63
+ })
64
+
65
+ // We only need to set up mqtt client and polling for 'main' accessory (channel 0)
66
+ if (accessory.context.channel === 0) {
67
+ // Set up the mqtt client for cloud devices to send and receive device updates
68
+ if (accessory.context.connection !== 'local') {
69
+ this.accessory.mqtt = new mqttClient(platform, this.accessory)
70
+ this.accessory.mqtt.connect()
71
+ }
72
+
73
+ // Always request a device update on startup, then start the interval for polling
74
+ setTimeout(() => this.requestUpdate(true), 2000)
75
+ this.accessory.refreshInterval = setInterval(
76
+ () => this.requestUpdate(),
77
+ this.pollInterval * 1000,
78
+ )
79
+ }
80
+
81
+ // Output the customised options to the log
82
+ const opts = JSON.stringify({
83
+ connection: this.accessory.context.connection,
84
+ hideChannels: this.accessory.context.options.hideChannels,
85
+ showAs: 'switch',
86
+ })
87
+ platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
88
+ }
89
+
90
+ async internalStateUpdate(value) {
91
+ try {
92
+ // Add the request to the queue so updates are sent apart
93
+ await this.queue.add(async () => {
94
+ // Don't continue if the state is the same as before
95
+ if (value === this.service.getCharacteristic(this.hapChar.On).value) {
96
+ return
97
+ }
98
+
99
+ // This flag stops the plugin from requesting updates while pending on others
100
+ this.updateInProgress = true
101
+
102
+ // Get the primary accessory instance to send the command
103
+ const accessory = this.accessory.context.channel === 0 ? this.accessory : this.priAcc
104
+
105
+ // Generate the payload and namespace for the correct device model
106
+ const namespace = 'Appliance.Control.ToggleX'
107
+ const payload = {
108
+ togglex: {
109
+ onoff: value ? 1 : 0,
110
+ channel: this.accessory.context.channel,
111
+ },
112
+ }
113
+
114
+ // Use the platform function to send the update to the device
115
+ await this.platform.sendUpdate(accessory, {
116
+ namespace,
117
+ payload,
118
+ })
119
+
120
+ // Update the cache
121
+ this.cacheState = value
122
+
123
+ // Add the entry to eve history and log
124
+ this.accessory.eveService.addEntry({ status: value ? 1 : 0 })
125
+
126
+ this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
127
+
128
+ // Update the other accessories of this device with the correct status
129
+ switch (this.accessory.context.channel) {
130
+ case 0: {
131
+ // Update all the sub accessories with the same status
132
+ for (let i = 1; i < this.accessory.context.channelCount; i += 1) {
133
+ const subAcc = this.devicesInHB.get(
134
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + i),
135
+ )
136
+ if (subAcc) {
137
+ const hapServ = subAcc.getService(this.hapServ.Switch)
138
+ const hapChar = hapServ.getCharacteristic(this.hapChar.On)
139
+ if (hapChar.value !== value) {
140
+ hapChar.updateValue(value)
141
+
142
+ // Add the entry to eve history and log
143
+ subAcc.eveService.addEntry({ status: value ? 1 : 0 })
144
+ subAcc.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
145
+ }
146
+ }
147
+ }
148
+ break
149
+ }
150
+ case 1:
151
+ case 2:
152
+ case 3:
153
+ case 4:
154
+ case 5:
155
+ case 6: {
156
+ let primaryState = false
157
+ for (let i = 1; i <= this.accessory.context.channelCount; i += 1) {
158
+ const subAcc = this.devicesInHB.get(
159
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + i),
160
+ )
161
+ if (subAcc) {
162
+ if (i === this.accessory.context.channel) {
163
+ if (value) {
164
+ primaryState = true
165
+ }
166
+ } else {
167
+ const hapServ = subAcc.getService(this.hapServ.Switch)
168
+ const hapChar = hapServ.getCharacteristic(this.hapChar.On)
169
+ if (hapChar.value) {
170
+ primaryState = true
171
+ }
172
+ }
173
+ }
174
+ }
175
+ if (!this.platform.hideMasters.includes(this.accessory.context.serialNumber)) {
176
+ const hapServ = this.priAcc.getService(this.hapServ.Switch)
177
+ const hapChar = hapServ.getCharacteristic(this.hapChar.On)
178
+ if (hapChar.value !== primaryState) {
179
+ hapChar.updateValue(primaryState)
180
+
181
+ // Add the entry to eve history and log
182
+ this.priAcc.eveService.addEntry({ status: primaryState ? 1 : 0 })
183
+ this.priAcc.log(`${platformLang.curState} [${primaryState ? 'on' : 'off'}]`)
184
+ }
185
+ }
186
+ break
187
+ }
188
+ default:
189
+ }
190
+ })
191
+ } catch (err) {
192
+ // Catch any errors whilst updating the device
193
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
194
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
195
+ setTimeout(() => {
196
+ this.service.updateCharacteristic(
197
+ this.hapChar.On,
198
+ this.service.getCharacteristic(this.hapChar.On).value,
199
+ )
200
+ }, 2000)
201
+ throw new this.hapErr(-70402)
202
+ }
203
+ }
204
+
205
+ async requestUpdate(firstRun = false) {
206
+ try {
207
+ // Don't continue if an update is currently being sent to the device
208
+ if (this.updateInProgress) {
209
+ return
210
+ }
211
+
212
+ // Add the request to the queue so updates are sent apart
213
+ await this.queue.add(async () => {
214
+ // This flag stops the plugin from requesting updates while pending on others
215
+ this.updateInProgress = true
216
+
217
+ // Send the request
218
+ const res = await this.platform.sendUpdate(this.accessory, {
219
+ namespace: 'Appliance.System.All',
220
+ payload: {},
221
+ })
222
+
223
+ // Log the received data
224
+ this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
225
+
226
+ // Check the response is in a useful format
227
+ const data = res.data.payload
228
+ if (data.all) {
229
+ if (
230
+ data.all.digest
231
+ && data.all.digest.togglex
232
+ && Array.isArray(data.all.digest.togglex)
233
+ ) {
234
+ this.applyUpdate(data.all.digest.togglex)
235
+ }
236
+
237
+ // A flag to check if we need to update the accessory context
238
+ let needsUpdate = false
239
+
240
+ // Get the mac address and hardware version of the device
241
+ if (data.all.system) {
242
+ // Mac address and hardware don't change regularly so only get on first poll
243
+ if (firstRun && data.all.system.hardware) {
244
+ this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
245
+ this.accessory.context.hardware = data.all.system.hardware.version
246
+ }
247
+
248
+ // Get the ip address and firmware of the device
249
+ if (data.all.system.firmware) {
250
+ // Check for an IP change each and every time the device is polled
251
+ if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
252
+ this.accessory.context.ipAddress = data.all.system.firmware.innerIp
253
+ needsUpdate = true
254
+ }
255
+
256
+ // Firmware doesn't change regularly so only get on first poll
257
+ if (firstRun) {
258
+ this.accessory.context.firmware = data.all.system.firmware.version
259
+ }
260
+ }
261
+ }
262
+
263
+ // Get the cloud online status of the device
264
+ if (data.all.system.online) {
265
+ const isOnline = data.all.system.online.status === 1
266
+ if (this.accessory.context.isOnline !== isOnline) {
267
+ this.accessory.context.isOnline = isOnline
268
+ needsUpdate = true
269
+ }
270
+ }
271
+
272
+ // Update the accessory cache if anything has changed
273
+ if (needsUpdate || firstRun) {
274
+ for (let i = 0; i <= this.accessory.context.channelCount; i += 1) {
275
+ const subAcc = this.devicesInHB.get(
276
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + i),
277
+ )
278
+ if (subAcc) {
279
+ subAcc.context = {
280
+ ...subAcc.context,
281
+
282
+ macAddress: this.accessory.context.macAddress,
283
+ hardware: this.accessory.context.hardware,
284
+ ipAddress: this.accessory.context.ipAddress,
285
+ firmware: this.accessory.context.firmware,
286
+ isOnline: this.accessory.context.isOnline
287
+ ,
288
+ }
289
+ this.platform.updateAccessory(subAcc)
290
+ }
291
+ }
292
+ }
293
+ }
294
+ })
295
+ } catch (err) {
296
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
297
+ this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
298
+
299
+ // Set the homebridge-ui status of the device to offline if local and error is timeout
300
+ if (
301
+ (this.accessory.context.isOnline || firstRun)
302
+ && ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
303
+ ) {
304
+ for (let i = 0; i <= this.accessory.context.channelCount; i += 1) {
305
+ const subAcc = this.devicesInHB.get(
306
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + i),
307
+ )
308
+ if (subAcc) {
309
+ subAcc.context.isOnline = false
310
+ this.platform.updateAccessory(subAcc)
311
+ }
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ receiveUpdate(params) {
318
+ try {
319
+ // Log the received data
320
+ this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
321
+
322
+ // Validate the response, checking for payload property
323
+ if (!params.payload) {
324
+ throw new Error('invalid response received')
325
+ }
326
+ const data = params.payload
327
+
328
+ // Check the data is in a format which contains the value we need
329
+ if (data.togglex) {
330
+ // payload.togglex can either be an array of objects (multiple channels) or a single object
331
+ // Either way, push all items into one array
332
+ const toUpdate = []
333
+ if (Array.isArray(data.togglex)) {
334
+ data.togglex.forEach(item => toUpdate.push(item))
335
+ } else {
336
+ toUpdate.push(data.togglex)
337
+ }
338
+ this.applyUpdate(toUpdate)
339
+ }
340
+ } catch (err) {
341
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
342
+ }
343
+ }
344
+
345
+ applyUpdate(data) {
346
+ data.forEach((channel) => {
347
+ // Attempt to find the accessory this channel relates to
348
+ const accessory = channel.channel === 0
349
+ ? this.accessory
350
+ : this.devicesInHB.get(
351
+ this.platform.api.hap.uuid.generate(
352
+ this.accessory.context.serialNumber + channel.channel,
353
+ ),
354
+ )
355
+
356
+ // Check the accessory exists
357
+ if (!accessory) {
358
+ return
359
+ }
360
+
361
+ // Obtain the service and current value
362
+ const hapServ = channel.channel === 0 ? this.service : accessory.getService(this.hapServ.Switch)
363
+ const hapChar = hapServ.getCharacteristic(this.hapChar.On)
364
+
365
+ // Read the current state
366
+ const newState = channel.onoff === 1
367
+
368
+ // Don't continue if the state is the same as before
369
+ if (hapChar.value === newState) {
370
+ return
371
+ }
372
+
373
+ // Update the HomeKit characteristics
374
+ hapChar.updateValue(newState)
375
+
376
+ // Add the entry to eve history and log
377
+ accessory.eveService.addEntry({ status: newState ? 1 : 0 })
378
+ this.accessory.log(`${platformLang.curState} [${newState ? 'on' : 'off'}]`)
379
+ })
380
+
381
+ // Check for the primary accessory state
382
+ if (this.platform.hideMasters.includes(this.accessory.context.serialNumber)) {
383
+ return
384
+ }
385
+ let primaryState = false
386
+ for (let i = 1; i <= this.accessory.context.channelCount; i += 1) {
387
+ const subAcc = this.devicesInHB.get(
388
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + i),
389
+ )
390
+ if (subAcc?.getService(this.hapServ.Switch).getCharacteristic(this.hapChar.On).value) {
391
+ primaryState = true
392
+ }
393
+ }
394
+ const hapChar = this.priAcc.getService(this.hapServ.Switch).getCharacteristic(this.hapChar.On)
395
+ if (hapChar.value !== primaryState) {
396
+ hapChar.updateValue(primaryState)
397
+
398
+ // Add the entry to eve history and log
399
+ this.priAcc.eveService.addEntry({ status: primaryState ? 1 : 0 })
400
+ this.priAcc.log(`${platformLang.curState} [${primaryState ? 'on' : 'off'}]`)
401
+ }
402
+ }
403
+ }