@homebridge-plugins/homebridge-wemo 7.0.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 (41) hide show
  1. package/CHANGELOG.md +809 -0
  2. package/LICENSE +21 -0
  3. package/README.md +66 -0
  4. package/config.schema.json +814 -0
  5. package/eslint.config.js +50 -0
  6. package/lib/connection/http.js +174 -0
  7. package/lib/connection/upnp.js +155 -0
  8. package/lib/device/coffee.js +167 -0
  9. package/lib/device/crockpot.js +380 -0
  10. package/lib/device/dimmer.js +280 -0
  11. package/lib/device/heater.js +416 -0
  12. package/lib/device/humidifier.js +379 -0
  13. package/lib/device/index.js +39 -0
  14. package/lib/device/insight.js +353 -0
  15. package/lib/device/lightswitch.js +154 -0
  16. package/lib/device/link-bulb.js +467 -0
  17. package/lib/device/link-hub.js +63 -0
  18. package/lib/device/maker-garage.js +426 -0
  19. package/lib/device/maker-switch.js +202 -0
  20. package/lib/device/motion.js +148 -0
  21. package/lib/device/outlet.js +148 -0
  22. package/lib/device/purifier.js +468 -0
  23. package/lib/device/simulation/purifier-insight.js +357 -0
  24. package/lib/device/simulation/purifier.js +159 -0
  25. package/lib/device/simulation/switch-insight.js +315 -0
  26. package/lib/device/simulation/switch.js +154 -0
  27. package/lib/fakegato/LICENSE +21 -0
  28. package/lib/fakegato/fakegato-history.js +814 -0
  29. package/lib/fakegato/fakegato-storage.js +108 -0
  30. package/lib/fakegato/fakegato-timer.js +125 -0
  31. package/lib/fakegato/uuid.js +27 -0
  32. package/lib/homebridge-ui/public/index.html +315 -0
  33. package/lib/homebridge-ui/server.js +10 -0
  34. package/lib/index.js +8 -0
  35. package/lib/platform.js +1423 -0
  36. package/lib/utils/colour.js +135 -0
  37. package/lib/utils/constants.js +129 -0
  38. package/lib/utils/eve-chars.js +130 -0
  39. package/lib/utils/functions.js +52 -0
  40. package/lib/utils/lang-en.js +125 -0
  41. package/package.json +70 -0
@@ -0,0 +1,426 @@
1
+ import { parseStringPromise } from 'xml2js'
2
+
3
+ import platformConsts from '../utils/constants.js'
4
+ import { decodeXML, parseError, sleep } from '../utils/functions.js'
5
+ import platformLang from '../utils/lang-en.js'
6
+
7
+ export default class {
8
+ constructor(platform, accessory) {
9
+ // Set up variables from the platform
10
+ this.eveChar = platform.eveChar
11
+ this.hapChar = platform.api.hap.Characteristic
12
+ this.hapErr = platform.api.hap.HapStatusError
13
+ this.hapServ = platform.api.hap.Service
14
+ this.platform = platform
15
+
16
+ // Set up variables from the accessory
17
+ this.accessory = accessory
18
+
19
+ // Set up custom variables for this device type
20
+ const deviceConf = platform.deviceConf[accessory.context.serialNumber] || {}
21
+ this.doorOpenTimer = deviceConf.makerTimer || platformConsts.defaultValues.makerTimer
22
+
23
+ // Some conversion objects
24
+ this.gStates = {
25
+ Open: 0,
26
+ Closed: 1,
27
+ Opening: 2,
28
+ Closing: 3,
29
+ Stopped: 4,
30
+ }
31
+
32
+ // If the accessory has a switch service then remove it
33
+ if (this.accessory.getService(this.hapServ.Switch)) {
34
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
35
+ }
36
+
37
+ // If the accessory has a contact sensor service then remove it
38
+ if (this.accessory.getService(this.hapServ.ContactSensor)) {
39
+ this.accessory.removeService(this.accessory.getService(this.hapServ.ContactSensor))
40
+ }
41
+
42
+ // Add the garage door service if it doesn't already exist
43
+ this.service = this.accessory.getService(this.hapServ.GarageDoorOpener)
44
+ if (!this.service) {
45
+ this.service = this.accessory.addService(this.hapServ.GarageDoorOpener)
46
+ this.service.addCharacteristic(this.eveChar.LastActivation)
47
+ this.service.addCharacteristic(this.eveChar.ResetTotal)
48
+ this.service.addCharacteristic(this.eveChar.TimesOpened)
49
+ }
50
+
51
+ // Remove unused characteristics
52
+ if (this.service.testCharacteristic(this.hapChar.ContactSensorState)) {
53
+ this.service.removeCharacteristic(
54
+ this.service.getCharacteristic(this.hapChar.ContactSensorState),
55
+ )
56
+ }
57
+ if (this.service.testCharacteristic(this.eveChar.OpenDuration)) {
58
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.eveChar.OpenDuration))
59
+ }
60
+ if (this.service.testCharacteristic(this.eveChar.ClosedDuration)) {
61
+ this.service.removeCharacteristic(this.service.getCharacteristic(this.eveChar.ClosedDuration))
62
+ }
63
+
64
+ // Add the set handler to the garage door reset total characteristic
65
+ this.service.getCharacteristic(this.eveChar.ResetTotal).onSet(() => {
66
+ this.service.updateCharacteristic(this.eveChar.TimesOpened, 0)
67
+ })
68
+
69
+ // Add the set handler to the target door state characteristic
70
+ this.service
71
+ .getCharacteristic(this.hapChar.TargetDoorState)
72
+ .removeOnSet()
73
+ .onSet(async value => this.internalStateUpdate(value))
74
+
75
+ // Pass the accessory to Fakegato to set up with Eve
76
+ this.accessory.eveService = new platform.eveService('door', this.accessory, {
77
+ log: () => {},
78
+ })
79
+ this.accessory.eveService.addEntry({
80
+ status: this.service.getCharacteristic(this.hapChar.CurrentDoorState).value === 0 ? 0 : 1,
81
+ })
82
+
83
+ // Output the customised options to the log
84
+ const opts = JSON.stringify({
85
+ makerTimer: this.doorOpenTimer,
86
+ })
87
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
88
+
89
+ // This is to remove the 'No Response' message that is there before the plugin finds this device
90
+ this.service.updateCharacteristic(
91
+ this.hapChar.TargetDoorState,
92
+ this.accessory.context.cacheLastTargetState,
93
+ )
94
+
95
+ // Request a device update immediately
96
+ this.requestDeviceUpdate()
97
+
98
+ // Start a polling interval if the user has disabled upnp
99
+ if (this.accessory.context.connection === 'http') {
100
+ this.pollingInterval = setInterval(
101
+ () => this.requestDeviceUpdate(),
102
+ platform.config.pollingInterval * 1000,
103
+ )
104
+ }
105
+ }
106
+
107
+ receiveDeviceUpdate(attribute) {
108
+ // Log the receiving update if debug is enabled
109
+ this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
110
+
111
+ // Check which attribute we are getting
112
+ switch (attribute.name) {
113
+ case 'Switch': {
114
+ if (attribute.value !== 0) {
115
+ this.externalStateUpdate()
116
+ }
117
+ break
118
+ }
119
+ case 'Sensor': {
120
+ this.externalSensorUpdate(attribute.value, true)
121
+ break
122
+ }
123
+ default:
124
+ }
125
+ }
126
+
127
+ async sendDeviceUpdate(value) {
128
+ // Log the sending update if debug is enabled
129
+ this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
130
+
131
+ // Send the update
132
+ await this.platform.httpClient.sendDeviceUpdate(
133
+ this.accessory,
134
+ 'urn:Belkin:service:basicevent:1',
135
+ 'SetBinaryState',
136
+ value,
137
+ )
138
+ }
139
+
140
+ async requestDeviceUpdate() {
141
+ try {
142
+ // Request the update
143
+ const data = await this.platform.httpClient.sendDeviceUpdate(
144
+ this.accessory,
145
+ 'urn:Belkin:service:deviceevent:1',
146
+ 'GetAttributes',
147
+ )
148
+
149
+ // Parse the response
150
+ const decoded = decodeXML(data.attributeList)
151
+ const xml = `<attributeList>${decoded}</attributeList>`
152
+ const result = await parseStringPromise(xml, { explicitArray: false })
153
+ const attributes = {}
154
+ Object.keys(result.attributeList.attribute).forEach((key) => {
155
+ const attribute = result.attributeList.attribute[key]
156
+ attributes[attribute.name] = Number.parseInt(attribute.value, 10)
157
+ })
158
+
159
+ // Only send the required attributes to the receiveDeviceUpdate function
160
+ if (attributes.SwitchMode === 0) {
161
+ this.accessory.logWarn(platformLang.makerNeedMMode)
162
+ return
163
+ }
164
+ if (attributes.SensorPresent === 1) {
165
+ this.sensorPresent = true
166
+ this.externalSensorUpdate(attributes.Sensor)
167
+ } else {
168
+ this.sensorPresent = false
169
+ }
170
+ } catch (err) {
171
+ const eText = parseError(err, [
172
+ platformLang.timeout,
173
+ platformLang.timeoutUnreach,
174
+ platformLang.noService,
175
+ ])
176
+ this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
177
+ }
178
+ }
179
+
180
+ async internalStateUpdate(value) {
181
+ const prevTarg = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
182
+ const prevCurr = this.service.getCharacteristic(this.hapChar.CurrentDoorState).value
183
+ try {
184
+ // Checks to see if the new required movement is already happening
185
+ if (this.isMoving) {
186
+ if (value === this.gStates.Closed && prevCurr === this.gStates.Closing) {
187
+ this.accessory.log(platformLang.makerClosing)
188
+ return
189
+ }
190
+ if (value === this.gStates.Open && prevCurr === this.gStates.Opening) {
191
+ this.accessory.log(platformLang.makerOpening)
192
+ return
193
+ }
194
+ } else if (value === this.gStates.Closed && prevCurr === this.gStates.Closed) {
195
+ this.accessory.log(platformLang.makerClosed)
196
+ return
197
+ } else if (value === this.gStates.Open && prevCurr === this.gStates.Open) {
198
+ this.accessory.log(platformLang.makerOpen)
199
+ return
200
+ }
201
+
202
+ // Required movement isn't already in progress so make the new movement happen
203
+ this.homekitTriggered = true
204
+
205
+ // Send the update
206
+ await this.sendDeviceUpdate({
207
+ BinaryState: 1,
208
+ })
209
+
210
+ // Log the change if appropriate
211
+ this.accessory.log(`${platformLang.tarState} [${value ? platformLang.labelClosed : platformLang.labelOpen}]`)
212
+
213
+ // Call the function to set the door moving
214
+ this.accessory.context.cacheLastTargetState = value
215
+ this.setDoorMoving(value, true)
216
+ } catch (err) {
217
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
218
+
219
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
220
+ setTimeout(() => {
221
+ this.service.updateCharacteristic(this.hapChar.TargetDoorState, prevTarg)
222
+ this.accessory.context.cacheLastTargetState = prevTarg
223
+ }, 2000)
224
+ throw new this.hapErr(-70402)
225
+ }
226
+ }
227
+
228
+ externalStateUpdate() {
229
+ try {
230
+ // We want to ignore update notifications from when controlled through HomeKit
231
+ if (this.homekitTriggered) {
232
+ this.homekitTriggered = false
233
+ return
234
+ }
235
+
236
+ // The change of state must have been triggered externally
237
+ const target = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
238
+ const state = 1 - target
239
+ this.accessory.log(
240
+ `${platformLang.tarState} [${state === 1 ? platformLang.labelClosed : platformLang.labelOpen}] [${platformLang.makerTrigExt}]`,
241
+ )
242
+
243
+ // Update the new target state HomeKit characteristic
244
+ this.service.updateCharacteristic(this.hapChar.TargetDoorState, state)
245
+ this.accessory.context.cacheLastTargetState = state
246
+
247
+ // If the door has been opened externally then update the Eve-only characteristics
248
+ if (state === 0) {
249
+ this.accessory.eveService.addEntry({ status: 0 })
250
+ this.service.updateCharacteristic(
251
+ this.eveChar.LastActivation,
252
+ Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
253
+ )
254
+ this.service.updateCharacteristic(
255
+ this.eveChar.TimesOpened,
256
+ this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
257
+ )
258
+ }
259
+ this.setDoorMoving(state)
260
+ } catch (err) {
261
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
262
+ }
263
+ }
264
+
265
+ externalSensorUpdate(state, wasTriggered) {
266
+ try {
267
+ // 0->1 and 1->0 reverse values to match HomeKit needs
268
+ const value = 1 - state
269
+ const target = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
270
+ if (target === 0) {
271
+ // CASE target is to OPEN
272
+ if (value === 0) {
273
+ // Garage door HK target state is OPEN and the sensor has reported OPEN
274
+ if (this.isMoving) {
275
+ // Garage door is in the process of opening
276
+ this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Opening)
277
+ this.accessory.eveService.addEntry({ status: 0 })
278
+ this.service.updateCharacteristic(
279
+ this.eveChar.LastActivation,
280
+ Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
281
+ )
282
+ this.service.updateCharacteristic(
283
+ this.eveChar.TimesOpened,
284
+ this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
285
+ )
286
+
287
+ // Log the change if appropriate
288
+ this.accessory.log(`${platformLang.curState} [${platformLang.labelOpening}]`)
289
+ } else {
290
+ // Garage door is open and not moving
291
+ this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Open)
292
+
293
+ // Log the change if appropriate
294
+ this.accessory.log(`${platformLang.curState} [${platformLang.labelOpen}]`)
295
+ }
296
+ } else {
297
+ // Garage door HK target state is OPEN and the sensor has reported CLOSED
298
+ // Must have been triggered externally
299
+ this.isMoving = false
300
+ this.service.updateCharacteristic(this.hapChar.TargetDoorState, this.gStates.Closed)
301
+ this.accessory.context.cacheLastTargetState = this.gStates.Closed
302
+ this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Closed)
303
+ this.accessory.eveService.addEntry({ status: 1 })
304
+
305
+ // Log the change if appropriate
306
+ this.accessory.log(`${platformLang.curState} [${platformLang.labelClosed}] [${platformLang.makerTrigExt}]`)
307
+ }
308
+ } else if (value === 1) {
309
+ // Garage door HK target state is CLOSED and the sensor has reported CLOSED
310
+ this.isMoving = false
311
+ if (this.movingTimer) {
312
+ clearTimeout(this.movingTimer)
313
+ this.movingTimer = false
314
+ }
315
+
316
+ // Update the HomeKit characteristics
317
+ this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Closed)
318
+ this.accessory.eveService.addEntry({ status: 1 })
319
+
320
+ // Log the change if appropriate
321
+ this.accessory.log(`${platformLang.curState} [${platformLang.labelClosed}]`)
322
+ } else {
323
+ // Garage door HK target state is CLOSED but the sensor has reported OPEN
324
+ // Must have been triggered externally
325
+ this.service.updateCharacteristic(this.hapChar.TargetDoorState, this.gStates.Open)
326
+ this.accessory.context.cacheLastTargetState = this.gStates.Open
327
+ this.accessory.eveService.addEntry({ status: 0 })
328
+ this.service.updateCharacteristic(
329
+ this.eveChar.LastActivation,
330
+ Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
331
+ )
332
+ this.service.updateCharacteristic(
333
+ this.eveChar.TimesOpened,
334
+ this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
335
+ )
336
+
337
+ // Log the change if appropriate
338
+ this.accessory.log(`${platformLang.tarState} [${platformLang.labelOpen}] [${platformLang.makerTrigExt}]`)
339
+ if (wasTriggered) {
340
+ this.setDoorMoving(0)
341
+ }
342
+ }
343
+ } catch (err) {
344
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
345
+ }
346
+ }
347
+
348
+ async setDoorMoving(targetDoorState, homekitTriggered) {
349
+ // If a moving timer already exists then stop it
350
+ if (this.movingTimer) {
351
+ clearTimeout(this.movingTimer)
352
+ this.movingTimer = false
353
+ }
354
+
355
+ // The door must have stopped
356
+ if (this.isMoving) {
357
+ this.isMoving = false
358
+ this.service.updateCharacteristic(this.hapChar.CurrentDoorState, 4)
359
+ this.accessory.log(`${platformLang.curState} [${platformLang.labelStopped}]`)
360
+
361
+ // Toggle TargetDoorState after receiving a stop
362
+ await sleep(500)
363
+ const target = targetDoorState === this.gStates.Open ? this.gStates.Closed : this.gStates.Open
364
+ this.service.updateCharacteristic(this.hapChar.TargetDoorState, target)
365
+ this.accessory.context.cacheLastTargetState = target
366
+ return
367
+ }
368
+
369
+ // Set the moving flag to true
370
+ this.isMoving = true
371
+ if (homekitTriggered) {
372
+ // CASE: triggered through HomeKit
373
+ const curState = this.service.getCharacteristic(this.hapChar.CurrentDoorState).value
374
+ if (targetDoorState === this.gStates.Closed) {
375
+ // CASE: triggered through HomeKit and requested to CLOSE
376
+ if (curState !== this.gStates.Closed) {
377
+ this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Closing)
378
+
379
+ // Log the change if appropriate
380
+ this.accessory.log(`${platformLang.curState} [${platformLang.labelClosing}]`)
381
+ }
382
+ } else if (
383
+ curState === this.gStates.Stopped
384
+ || (curState !== this.gStates.Open && !this.sensorPresent)
385
+ ) {
386
+ // CASE: triggered through HomeKit and requested to OPEN
387
+ this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Opening)
388
+ this.accessory.eveService.addEntry({ status: 0 })
389
+ this.service.updateCharacteristic(
390
+ this.eveChar.LastActivation,
391
+ Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
392
+ )
393
+ this.service.updateCharacteristic(
394
+ this.eveChar.TimesOpened,
395
+ this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
396
+ )
397
+
398
+ // Log the change if appropriate
399
+ this.accessory.log(`${platformLang.curState} [${platformLang.labelOpening}]`)
400
+ }
401
+ }
402
+
403
+ // Set up the moving timer
404
+ this.movingTimer = setTimeout(() => {
405
+ this.movingTimer = false
406
+ this.isMoving = false
407
+ const target = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
408
+ if (!this.sensorPresent) {
409
+ this.service.updateCharacteristic(
410
+ this.hapChar.CurrentDoorState,
411
+ target === 1 ? this.gStates.Closed : this.gStates.Open,
412
+ )
413
+
414
+ // Log the change if appropriate
415
+ this.accessory.log(`${platformLang.curState} [${target === 1 ? platformLang.labelClosed : platformLang.labelOpen}]`)
416
+ return
417
+ }
418
+ if (target === 1) {
419
+ this.accessory.eveService.addEntry({ status: 1 })
420
+ }
421
+
422
+ // Request a device update at the end of the timer
423
+ this.requestDeviceUpdate()
424
+ }, this.doorOpenTimer * 1000)
425
+ }
426
+ }
@@ -0,0 +1,202 @@
1
+ import { parseStringPromise } from 'xml2js'
2
+
3
+ import { decodeXML, parseError } from '../utils/functions.js'
4
+ import platformLang from '../utils/lang-en.js'
5
+
6
+ export default class {
7
+ constructor(platform, accessory) {
8
+ // Set up variables from the platform
9
+ this.hapChar = platform.api.hap.Characteristic
10
+ this.hapErr = platform.api.hap.HapStatusError
11
+ this.hapServ = platform.api.hap.Service
12
+ this.platform = platform
13
+
14
+ // Set up variables from the accessory
15
+ this.accessory = accessory
16
+
17
+ // Set up custom variables for this device type
18
+ const deviceConf = platform.deviceConf[accessory.context.serialNumber] || {}
19
+ this.reversePolarity = deviceConf.reversePolarity
20
+
21
+ // If the accessory has a garage door service then remove it
22
+ if (this.accessory.getService(this.hapServ.GarageDoorOpener)) {
23
+ this.accessory.removeService(this.accessory.getService(this.hapServ.GarageDoorOpener))
24
+ }
25
+
26
+ // Add the switch service if it doesn't already exist
27
+ this.service = this.accessory.getService(this.hapServ.Switch)
28
+ || this.accessory.addService(this.hapServ.Switch)
29
+
30
+ // This is used to remove any no response status on startup
31
+ this.service.updateCharacteristic(
32
+ this.hapChar.On,
33
+ this.service.getCharacteristic(this.hapChar.On).value || false,
34
+ )
35
+
36
+ // Add the set handler to the switch on/off characteristic
37
+ this.service
38
+ .getCharacteristic(this.hapChar.On)
39
+ .removeOnSet()
40
+ .onSet(async value => this.internalStateUpdate(value))
41
+
42
+ // Output the customised options to the log
43
+ const opts = JSON.stringify({
44
+ })
45
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
46
+
47
+ // Request a device update immediately
48
+ this.requestDeviceUpdate()
49
+
50
+ // Start a polling interval if the user has disabled upnp
51
+ if (this.accessory.context.connection === 'http') {
52
+ this.pollingInterval = setInterval(
53
+ () => this.requestDeviceUpdate(),
54
+ platform.config.pollingInterval * 1000,
55
+ )
56
+ }
57
+ }
58
+
59
+ receiveDeviceUpdate(attribute) {
60
+ // Log the receiving update if debug is enabled
61
+ this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
62
+
63
+ // Check which attribute we are getting
64
+ switch (attribute.name) {
65
+ case 'Switch': {
66
+ const hkValue = attribute.value === 1
67
+ this.externalStateUpdate(hkValue)
68
+ break
69
+ }
70
+ case 'Sensor':
71
+ this.externalSensorUpdate(attribute.value)
72
+ break
73
+ default:
74
+ }
75
+ }
76
+
77
+ async sendDeviceUpdate(value) {
78
+ // Log the sending update if debug is enabled
79
+ this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
80
+
81
+ // Send the update
82
+ await this.platform.httpClient.sendDeviceUpdate(
83
+ this.accessory,
84
+ 'urn:Belkin:service:basicevent:1',
85
+ 'SetBinaryState',
86
+ value,
87
+ )
88
+ }
89
+
90
+ async requestDeviceUpdate() {
91
+ try {
92
+ // Request the update
93
+ const data = await this.platform.httpClient.sendDeviceUpdate(
94
+ this.accessory,
95
+ 'urn:Belkin:service:deviceevent:1',
96
+ 'GetAttributes',
97
+ )
98
+
99
+ // Parse the response
100
+ const decoded = decodeXML(data.attributeList)
101
+ const xml = `<attributeList>${decoded}</attributeList>`
102
+ const result = await parseStringPromise(xml, { explicitArray: false })
103
+ const attributes = {}
104
+ Object.keys(result.attributeList.attribute).forEach((key) => {
105
+ const attribute = result.attributeList.attribute[key]
106
+ attributes[attribute.name] = Number.parseInt(attribute.value, 10)
107
+ })
108
+
109
+ // Only send the required attributes to the receiveDeviceUpdate function
110
+ if (attributes.Switch) {
111
+ this.externalStateUpdate(attributes.Switch === 1)
112
+ }
113
+
114
+ // Check to see if the accessory has a contact sensor
115
+ const contactSensor = this.accessory.getService(this.hapServ.ContactSensor)
116
+ if (attributes.SensorPresent === 1) {
117
+ // Add a contact sensor service if the physical device has one
118
+ if (!contactSensor) {
119
+ this.accessory.addService(this.hapServ.ContactSensor)
120
+ }
121
+ if (attributes.Sensor) {
122
+ this.externalSensorUpdate(attributes.Sensor)
123
+ }
124
+ } else if (contactSensor) {
125
+ // Remove the contact sensor service if the physical device doesn't have one
126
+ this.accessory.removeService(contactSensor)
127
+ }
128
+ } catch (err) {
129
+ const eText = parseError(err, [
130
+ platformLang.timeout,
131
+ platformLang.timeoutUnreach,
132
+ platformLang.noService,
133
+ ])
134
+ this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
135
+ }
136
+ }
137
+
138
+ async internalStateUpdate(value) {
139
+ try {
140
+ // Send the update
141
+ await this.sendDeviceUpdate({
142
+ BinaryState: value ? 1 : 0,
143
+ })
144
+
145
+ // Update the cache and log if appropriate
146
+ this.cacheState = value
147
+ this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
148
+ } catch (err) {
149
+ const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
150
+ this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
151
+
152
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
153
+ setTimeout(() => {
154
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState)
155
+ }, 2000)
156
+ throw new this.hapErr(-70402)
157
+ }
158
+ }
159
+
160
+ externalStateUpdate(value) {
161
+ try {
162
+ // Don't continue if the value is the same as before
163
+ if (value === this.cacheState) {
164
+ return
165
+ }
166
+
167
+ // Update the HomeKit characteristic
168
+ this.service.updateCharacteristic(this.hapChar.On, value)
169
+
170
+ // Update the cache and log if appropriate
171
+ this.cacheState = value
172
+ this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
173
+ } catch (err) {
174
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
175
+ }
176
+ }
177
+
178
+ externalSensorUpdate(value) {
179
+ try {
180
+ // Reverse the polarity if enabled by user
181
+ if (this.reversePolarity) {
182
+ value = 1 - value
183
+ }
184
+
185
+ // Don't continue if the sensor value is the same as before
186
+ if (value === this.cacheContact) {
187
+ return
188
+ }
189
+
190
+ // Update the HomeKit characteristic
191
+ this.accessory
192
+ .getService(this.hapServ.ContactSensor)
193
+ .updateCharacteristic(this.hapChar.ContactSensorState, value)
194
+
195
+ // Update the cache and log the change if appropriate
196
+ this.cacheContact = value
197
+ this.accessory.log(`${platformLang.curCont} [${value === 1 ? platformLang.detectedNo : platformLang.detectedYes}]`)
198
+ } catch (err) {
199
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
200
+ }
201
+ }
202
+ }