@homebridge-plugins/homebridge-ewelink 12.3.3

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 (113) hide show
  1. package/CHANGELOG.md +1363 -0
  2. package/LICENSE +21 -0
  3. package/README.md +68 -0
  4. package/config.schema.json +1757 -0
  5. package/eslint.config.js +15 -0
  6. package/lib/connection/api.js +508 -0
  7. package/lib/connection/http.js +304 -0
  8. package/lib/connection/lan.js +318 -0
  9. package/lib/connection/ws.js +414 -0
  10. package/lib/device/curtain.js +324 -0
  11. package/lib/device/diffuser.js +473 -0
  12. package/lib/device/fan.js +349 -0
  13. package/lib/device/group.js +106 -0
  14. package/lib/device/humidifier.js +199 -0
  15. package/lib/device/index.js +61 -0
  16. package/lib/device/light-cct.js +333 -0
  17. package/lib/device/light-dimmer.js +257 -0
  18. package/lib/device/light-rgb-cct.js +809 -0
  19. package/lib/device/light-rgb.js +272 -0
  20. package/lib/device/motor.js +363 -0
  21. package/lib/device/outlet-multi.js +417 -0
  22. package/lib/device/outlet-single-inched.js +409 -0
  23. package/lib/device/outlet-single.js +384 -0
  24. package/lib/device/panel.js +382 -0
  25. package/lib/device/rf-bridge.js +321 -0
  26. package/lib/device/rf-button.js +125 -0
  27. package/lib/device/rf-sensor.js +173 -0
  28. package/lib/device/sensor-ambient.js +311 -0
  29. package/lib/device/sensor-contact.js +140 -0
  30. package/lib/device/sensor-temp-humi.js +182 -0
  31. package/lib/device/simulation/blind.js +253 -0
  32. package/lib/device/simulation/cooler.js +312 -0
  33. package/lib/device/simulation/door.js +253 -0
  34. package/lib/device/simulation/doorbell.js +190 -0
  35. package/lib/device/simulation/garage-eachen.js +235 -0
  36. package/lib/device/simulation/garage-four.js +176 -0
  37. package/lib/device/simulation/garage-od-switch.js +36 -0
  38. package/lib/device/simulation/garage-one.js +395 -0
  39. package/lib/device/simulation/garage-two.js +394 -0
  40. package/lib/device/simulation/gate-one.js +327 -0
  41. package/lib/device/simulation/heater.js +389 -0
  42. package/lib/device/simulation/index.js +75 -0
  43. package/lib/device/simulation/light-fan.js +258 -0
  44. package/lib/device/simulation/lock-eachen.js +163 -0
  45. package/lib/device/simulation/lock-one.js +366 -0
  46. package/lib/device/simulation/p-button.js +223 -0
  47. package/lib/device/simulation/purifier.js +289 -0
  48. package/lib/device/simulation/rf-blind.js +192 -0
  49. package/lib/device/simulation/rf-door.js +180 -0
  50. package/lib/device/simulation/rf-window.js +180 -0
  51. package/lib/device/simulation/sensor-hidden.js +105 -0
  52. package/lib/device/simulation/sensor-leak.js +123 -0
  53. package/lib/device/simulation/sensor-visible.js +231 -0
  54. package/lib/device/simulation/sensor.js +300 -0
  55. package/lib/device/simulation/switch-valve.js +351 -0
  56. package/lib/device/simulation/tap-one.js +283 -0
  57. package/lib/device/simulation/tap-two.js +266 -0
  58. package/lib/device/simulation/th-cooler.js +443 -0
  59. package/lib/device/simulation/th-dehumidifier.js +424 -0
  60. package/lib/device/simulation/th-heater.js +449 -0
  61. package/lib/device/simulation/th-humidifier.js +424 -0
  62. package/lib/device/simulation/th-thermostat.js +611 -0
  63. package/lib/device/simulation/tv.js +252 -0
  64. package/lib/device/simulation/valve-four.js +232 -0
  65. package/lib/device/simulation/valve-one.js +345 -0
  66. package/lib/device/simulation/valve-two.js +340 -0
  67. package/lib/device/simulation/window.js +253 -0
  68. package/lib/device/switch-man.js +106 -0
  69. package/lib/device/switch-mate.js +82 -0
  70. package/lib/device/switch-multi.js +411 -0
  71. package/lib/device/switch-single-inched.js +332 -0
  72. package/lib/device/switch-single.js +307 -0
  73. package/lib/device/template.js +43 -0
  74. package/lib/device/thermostat.js +286 -0
  75. package/lib/device/zigbee/index.js +27 -0
  76. package/lib/device/zigbee/light-cct.js +263 -0
  77. package/lib/device/zigbee/light-dimmer.js +148 -0
  78. package/lib/device/zigbee/light-rgb-cct.js +400 -0
  79. package/lib/device/zigbee/motor.js +301 -0
  80. package/lib/device/zigbee/sensor-ambient.js +177 -0
  81. package/lib/device/zigbee/sensor-contact.js +137 -0
  82. package/lib/device/zigbee/sensor-leak.js +119 -0
  83. package/lib/device/zigbee/sensor-motion.js +119 -0
  84. package/lib/device/zigbee/sensor-occupancy.js +119 -0
  85. package/lib/device/zigbee/sensor-smoke.js +96 -0
  86. package/lib/device/zigbee/switch-stateless.js +104 -0
  87. package/lib/device/zigbee/thermostat.js +323 -0
  88. package/lib/fakegato/LICENSE +21 -0
  89. package/lib/fakegato/fakegato-history.js +813 -0
  90. package/lib/fakegato/fakegato-storage.js +108 -0
  91. package/lib/fakegato/fakegato-timer.js +125 -0
  92. package/lib/fakegato/uuid.js +27 -0
  93. package/lib/homebridge-ui/public/index.html +365 -0
  94. package/lib/homebridge-ui/server.js +46 -0
  95. package/lib/index.js +7 -0
  96. package/lib/node-dns-sd/LICENSE +21 -0
  97. package/lib/node-dns-sd/README.md +613 -0
  98. package/lib/node-dns-sd/lib/dns-sd-classes.json +6 -0
  99. package/lib/node-dns-sd/lib/dns-sd-composer.js +83 -0
  100. package/lib/node-dns-sd/lib/dns-sd-parser.js +344 -0
  101. package/lib/node-dns-sd/lib/dns-sd-types.json +52 -0
  102. package/lib/node-dns-sd/lib/dns-sd.js +580 -0
  103. package/lib/platform.js +2156 -0
  104. package/lib/utils/colour.js +552 -0
  105. package/lib/utils/constants.js +589 -0
  106. package/lib/utils/custom-chars.js +33 -0
  107. package/lib/utils/eve-chars.js +130 -0
  108. package/lib/utils/functions.js +42 -0
  109. package/lib/utils/lang-en.js +160 -0
  110. package/lib/utils/lang-fr.js +163 -0
  111. package/lib/utils/lang-th.js +160 -0
  112. package/lib/utils/lang.js +5 -0
  113. package/package.json +75 -0
@@ -0,0 +1,2156 @@
1
+ import { createServer } from 'node:http'
2
+ import { createRequire } from 'node:module'
3
+ import { join } from 'node:path'
4
+ import process from 'node:process'
5
+ import storage from 'node-persist'
6
+ import PQueue from 'p-queue'
7
+ import apiClient from './connection/api.js'
8
+ import httpClient from './connection/http.js'
9
+ import lanClient from './connection/lan.js'
10
+ import wsClient from './connection/ws.js'
11
+ import deviceTypes from './device/index.js'
12
+ import eveService from './fakegato/fakegato-history.js'
13
+ import platformConsts from './utils/constants.js'
14
+ import platformChars from './utils/custom-chars.js'
15
+ import eveChars from './utils/eve-chars.js'
16
+ import { parseDeviceId, parseError } from './utils/functions.js'
17
+ import platformLang from './utils/lang.js'
18
+
19
+ const require = createRequire(import.meta.url)
20
+ const plugin = require('../package.json')
21
+
22
+ const devicesInHB = new Map()
23
+ const queue = new PQueue({
24
+ interval: 250,
25
+ intervalCap: 1,
26
+ timeout: 10000,
27
+ })
28
+
29
+ export default class {
30
+ constructor(log, config, api) {
31
+ if (!log || !api) {
32
+ return
33
+ }
34
+
35
+ // Begin plugin initialisation
36
+ try {
37
+ this.api = api
38
+ this.log = log
39
+ this.isBeta = plugin.version.includes('beta')
40
+ this.apiClient = false
41
+ this.apiServer = false
42
+ this.httpClient = false
43
+ this.lanClient = false
44
+ this.lanDevices = false
45
+ this.wsClient = false
46
+
47
+ // Configuration objects for accessories
48
+ this.deviceConf = {}
49
+ this.rfSubdevices = {}
50
+ this.hideChannels = []
51
+ this.hideMasters = []
52
+ this.ignoredDevices = []
53
+ this.ipOverride = {}
54
+ this.obstructSwitches = {}
55
+
56
+ // Retrieve the user's chosen language file
57
+ const lang = platformConsts.allowed.language.includes(config.language)
58
+ ? config.language
59
+ : platformConsts.defaultValues.language
60
+ this.lang = platformLang[lang]
61
+
62
+ // Make sure user is running Homebridge v1.5 or above
63
+ if (!api?.versionGreaterOrEqual('1.5.0')) {
64
+ throw new Error(this.lang.hbVersionFail)
65
+ }
66
+
67
+ // Check the user has configured the plugin
68
+ if (!config) {
69
+ throw new Error(this.lang.pluginNotConf)
70
+ }
71
+
72
+ // Log some environment info for debugging
73
+ this.log(
74
+ '%s v%s | System %s | Node %s | HB v%s | HAPNodeJS v%s...',
75
+ this.lang.initialising,
76
+ plugin.version,
77
+ process.platform,
78
+ process.version,
79
+ api.serverVersion,
80
+ api.hap.HAPLibraryVersion(),
81
+ )
82
+
83
+ // Apply the user's configuration
84
+ this.config = platformConsts.defaultConfig
85
+ this.applyUserConfig(config)
86
+
87
+ // Set up the Homebridge events
88
+ this.api.on('didFinishLaunching', () => this.pluginSetup())
89
+ this.api.on('shutdown', () => this.pluginShutdown())
90
+ } catch (err) {
91
+ // Catch any errors during initialisation
92
+ const eText = parseError(err, [this.lang.hbVersionFail, this.lang.pluginNotConf])
93
+ log.warn('***** %s. *****', this.lang.disabling)
94
+ log.warn('***** %s. *****', eText)
95
+ }
96
+ }
97
+
98
+ applyUserConfig(config) {
99
+ // These shorthand functions save line space during config parsing
100
+ const logDefault = (k, def) => {
101
+ this.log.warn('%s [%s] %s %s.', this.lang.cfgItem, k, this.lang.cfgDef, def)
102
+ }
103
+ const logDuplicate = (k) => {
104
+ this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgDup)
105
+ }
106
+ const logIgnore = (k) => {
107
+ this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgIgn)
108
+ }
109
+ const logIgnoreItem = (k) => {
110
+ this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgIgnItem)
111
+ }
112
+ const logIncrease = (k, min) => {
113
+ this.log.warn('%s [%s] %s %s.', this.lang.cfgItem, k, this.lang.cfgLow, min)
114
+ }
115
+ const logQuotes = (k) => {
116
+ this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgQts)
117
+ }
118
+ const logRemove = (k) => {
119
+ this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgRmv)
120
+ }
121
+
122
+ // Begin applying the user's config
123
+ Object.entries(config).forEach((entry) => {
124
+ const [key, val] = entry
125
+ switch (key) {
126
+ case 'apiPort': {
127
+ if (typeof v === 'string') {
128
+ logQuotes(key)
129
+ }
130
+ const intVal = Number.parseInt(val, 10)
131
+ if (Number.isNaN(intVal)) {
132
+ logDefault(key, platformConsts.defaultValues[key])
133
+ this.config.apiPort = platformConsts.defaultValues[key]
134
+ } else if (intVal < platformConsts.minValues[key]) {
135
+ logIncrease(key, platformConsts.minValues[key])
136
+ this.config.apiPort = platformConsts.minValues[key]
137
+ } else {
138
+ this.config.apiPort = intVal
139
+ }
140
+ break
141
+ }
142
+ case 'appId':
143
+ case 'appSecret':
144
+ case 'ignoredHomes':
145
+ case 'username':
146
+ if (typeof val !== 'string') {
147
+ logIgnore(key)
148
+ } else {
149
+ this.config[key] = val.replace(/\s+/g, '')
150
+ }
151
+ break
152
+ case 'bridgeSensors':
153
+ if (Array.isArray(val) && val.length > 0) {
154
+ val.forEach((x) => {
155
+ if (!x.fullDeviceId) {
156
+ logIgnoreItem(key)
157
+ return
158
+ }
159
+ const id = parseDeviceId(x.fullDeviceId)
160
+ if (Object.keys(this.rfSubdevices).includes(id)) {
161
+ logDuplicate(`${key}.${id}`)
162
+ return
163
+ }
164
+ const entries = Object.entries(x)
165
+ if (entries.length === 1) {
166
+ logRemove(`${key}.${id}`)
167
+ return
168
+ }
169
+ this.rfSubdevices[id] = {}
170
+ entries.forEach((subEntry) => {
171
+ const [k, v] = subEntry
172
+ switch (k) {
173
+ case 'curtainType':
174
+ case 'deviceType':
175
+ case 'type': {
176
+ const index = k === 'type' ? 'sensorType' : k
177
+ const inSet = platformConsts.allowed[index].includes(v)
178
+ if (typeof v !== 'string' || !inSet) {
179
+ logIgnore(`${key}.${id}.${k}`)
180
+ } else {
181
+ this.rfSubdevices[id][k] = inSet ? v : platformConsts.defaultValues[k]
182
+ }
183
+ break
184
+ }
185
+ case 'fullDeviceId':
186
+ case 'label':
187
+ break
188
+ case 'operationTime':
189
+ case 'operationTimeDown':
190
+ case 'sensorTimeDifference':
191
+ case 'sensorTimeLength': {
192
+ if (typeof v === 'string') {
193
+ logQuotes(`${key}.${id}.${k}`)
194
+ }
195
+ const intVal = Number.parseInt(v, 10)
196
+ if (Number.isNaN(intVal)) {
197
+ logDefault(`${key}.${id}.${k}`, platformConsts.defaultValues[k])
198
+ this.rfSubdevices[id][k] = platformConsts.defaultValues[k]
199
+ } else if (intVal < platformConsts.minValues[k]) {
200
+ logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k])
201
+ this.rfSubdevices[id][k] = platformConsts.minValues[k]
202
+ } else {
203
+ this.rfSubdevices[id][k] = intVal
204
+ }
205
+ break
206
+ }
207
+ case 'sensorWebHook':
208
+ if (typeof v !== 'string' || v === '') {
209
+ logIgnore(`${key}.${id}.${k}`)
210
+ } else {
211
+ this.rfSubdevices[id][k] = v
212
+ }
213
+ break
214
+ default:
215
+ logRemove(`${key}.${id}.${k}`)
216
+ break
217
+ }
218
+ })
219
+ })
220
+ } else {
221
+ logIgnore(key)
222
+ }
223
+ break
224
+ case 'countryCode':
225
+ if (typeof val !== 'string' || val === '') {
226
+ logIgnore(key)
227
+ } else {
228
+ this.config.countryCode = `+${val.replace(/\D/g, '')}`
229
+ }
230
+ break
231
+ case 'disableDeviceLogging':
232
+ case 'disableNoResponse':
233
+ if (typeof val === 'string') {
234
+ logQuotes(key)
235
+ }
236
+ this.config[key] = val === 'false' ? false : !!val
237
+ break
238
+ case 'fanDevices':
239
+ case 'lightDevices':
240
+ case 'multiDevices':
241
+ case 'rfDevices':
242
+ case 'sensorDevices':
243
+ case 'singleDevices':
244
+ case 'thDevices':
245
+ if (Array.isArray(val) && val.length > 0) {
246
+ val.forEach((x) => {
247
+ if (!x.deviceId) {
248
+ logIgnoreItem(key)
249
+ return
250
+ }
251
+ const id = parseDeviceId(x.deviceId)
252
+ if (Object.keys(this.deviceConf).includes(id)) {
253
+ logDuplicate(`${key}.${id}`)
254
+ return
255
+ }
256
+ const entries = Object.entries(x)
257
+ if (entries.length === 1) {
258
+ logRemove(`${key}.${id}`)
259
+ return
260
+ }
261
+ this.deviceConf[id] = {}
262
+ entries.forEach((subEntry) => {
263
+ const [k, v] = subEntry
264
+ switch (k) {
265
+ case 'adaptiveLightingShift':
266
+ case 'brightnessStep':
267
+ case 'inUsePowerThreshold':
268
+ case 'lowBattThreshold':
269
+ case 'minTarget':
270
+ case 'maxTarget':
271
+ case 'operationTime':
272
+ case 'operationTimeDown':
273
+ case 'sensorTimeDifference': {
274
+ if (typeof v === 'string') {
275
+ logQuotes(`${key}.${id}.${k}`)
276
+ }
277
+ const intVal = Number.parseInt(v, 10)
278
+ if (Number.isNaN(intVal)) {
279
+ logDefault(`${key}.${id}.${k}`, platformConsts.defaultValues[k])
280
+ this.deviceConf[id][k] = platformConsts.defaultValues[k]
281
+ } else if (intVal < platformConsts.minValues[k]) {
282
+ logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k])
283
+ this.deviceConf[id][k] = platformConsts.minValues[k]
284
+ } else {
285
+ this.deviceConf[id][k] = intVal
286
+ }
287
+ break
288
+ }
289
+ case 'deviceId':
290
+ case 'label':
291
+ break
292
+ case 'deviceModel':
293
+ if (typeof v !== 'string' || v === '') {
294
+ logIgnore(`${key}.${id}.${k}`)
295
+ } else if (!platformConsts.allowed.models[key].includes(v)) {
296
+ logIgnore(`${key}.${id}.${k}`)
297
+ } else if (v === 'gddc5' && key === 'singleDevices') {
298
+ this.deviceConf[id].showAs = 'garage_eachen'
299
+ }
300
+ break
301
+ case 'disableTimer':
302
+ case 'hideLight':
303
+ case 'hideLongDouble':
304
+ case 'hideSensor':
305
+ case 'hideSwitch':
306
+ case 'humidityOffsetFactor':
307
+ case 'isInched':
308
+ case 'offlineAsOff':
309
+ case 'offsetFactor':
310
+ case 'resetOnStartup':
311
+ case 'scaleBattery':
312
+ case 'showHeatCool':
313
+ if (typeof v === 'string') {
314
+ logQuotes(`${key}.${id}.${k}`)
315
+ }
316
+ this.deviceConf[id][k] = v === 'false' ? false : !!v
317
+ break
318
+ case 'hideChannels': {
319
+ if (typeof v !== 'string' || v === '') {
320
+ logIgnore(`${key}.${id}.${k}`)
321
+ } else {
322
+ const channels = v.split(',')
323
+ channels.forEach((channel) => {
324
+ this.hideChannels.push(`${id}SW${channel.replace(/\D+/g, '')}`)
325
+ this.deviceConf[id][k] = v
326
+ })
327
+ }
328
+ break
329
+ }
330
+ case 'humidityOffset':
331
+ case 'offset':
332
+ case 'targetTempThreshold': {
333
+ if (typeof v === 'string') {
334
+ logQuotes(`${key}.${id}.${k}`)
335
+ }
336
+ const numVal = Number(v)
337
+ if (Number.isNaN(numVal)) {
338
+ logIgnore(`${key}.${id}.${k}`)
339
+ } else {
340
+ this.deviceConf[id][k] = numVal
341
+ }
342
+ break
343
+ }
344
+ case 'ignoreDevice':
345
+ if (typeof v === 'string') {
346
+ logQuotes(`${key}.${id}.${k}`)
347
+ }
348
+ if (!!v && v !== 'false') {
349
+ this.ignoredDevices.push(id)
350
+ }
351
+ break
352
+ case 'inchChannels':
353
+ case 'temperatureSource':
354
+ if (typeof v !== 'string' || v === '') {
355
+ logIgnore(`${key}.${id}.${k}`)
356
+ } else {
357
+ this.deviceConf[id][k] = v
358
+ }
359
+ break
360
+ case 'ipAddress': {
361
+ if (typeof v !== 'string' || v === '') {
362
+ logIgnore(`${key}.${id}.${k}`)
363
+ } else {
364
+ this.ipOverride[id] = v
365
+ }
366
+ break
367
+ }
368
+ case 'obstructId':
369
+ if (typeof v !== 'string' || v === '') {
370
+ logIgnore(`${key}.${id}.${k}`)
371
+ } else {
372
+ const parsed = parseDeviceId(v)
373
+ this.deviceConf[id][k] = parsed
374
+ this.obstructSwitches[parsed] = id
375
+ }
376
+ break
377
+ case 'sensorId':
378
+ if (typeof v !== 'string' || v === '') {
379
+ logIgnore(`${key}.${id}.${k}`)
380
+ } else {
381
+ this.deviceConf[id].sensorId = parseDeviceId(v)
382
+ }
383
+ break
384
+ case 'sensorType':
385
+ case 'showAs':
386
+ case 'showAsEachen':
387
+ case 'showAsMotor': {
388
+ const inSet = platformConsts.allowed[k].includes(v)
389
+ if (typeof v !== 'string' || !inSet) {
390
+ logIgnore(`${key}.${id}.${k}`)
391
+ } else {
392
+ this.deviceConf[id][k] = inSet ? v : platformConsts.defaultValues[k]
393
+ }
394
+ break
395
+ }
396
+ default:
397
+ logRemove(`${key}.${id}.${k}`)
398
+ }
399
+ })
400
+ })
401
+ } else {
402
+ logIgnore(key)
403
+ }
404
+ break
405
+ case 'httpHost': {
406
+ const inSet = platformConsts.allowed.httpHosts.includes(val)
407
+ if (typeof val !== 'string' || !inSet) {
408
+ logIgnore(key)
409
+ }
410
+ const defaultAutoHost = val === 'auto' ? platformConsts.defaultValues[key] : val
411
+ this.config.httpHost = inSet ? defaultAutoHost : platformConsts.defaultValues[key]
412
+ break
413
+ }
414
+ case 'language':
415
+ case 'mode': {
416
+ const inSet = platformConsts.allowed[key].includes(val)
417
+ if (typeof val !== 'string' || !inSet) {
418
+ logIgnore(key)
419
+ }
420
+ this.config[key] = inSet ? val : platformConsts.defaultValues[key]
421
+ break
422
+ }
423
+ case 'name':
424
+ case 'platform':
425
+ break
426
+ case 'password':
427
+ if (typeof val !== 'string') {
428
+ logIgnore(key)
429
+ } else {
430
+ this.config.password = val
431
+ }
432
+ break
433
+ default:
434
+ logRemove(key)
435
+ break
436
+ }
437
+ })
438
+ }
439
+
440
+ async pluginSetup() {
441
+ // Plugin has finished initialising so now onto setup
442
+ try {
443
+ // Log that the plugin initialisation has been successful
444
+ this.log('%s.', this.lang.initialised)
445
+
446
+ // Sort out some logging functions
447
+ if (this.isBeta) {
448
+ this.log.debug = this.log
449
+ this.log.debugWarn = this.log.warn
450
+
451
+ // Log that using a beta will generate a lot of debug logs
452
+ if (this.isBeta) {
453
+ const divide = '*'.repeat(this.lang.beta.length + 1) // don't forget the full stop (+1!)
454
+ this.log.warn(divide)
455
+ this.log.warn(`${this.lang.beta}.`)
456
+ this.log.warn(divide)
457
+ }
458
+ } else {
459
+ this.log.debug = () => {}
460
+ this.log.debugWarn = () => {}
461
+ }
462
+
463
+ // Check the eWeLink credentials are configured (except lan mode)
464
+ if (this.config.mode !== 'lan' && (!this.config.username || !this.config.password)) {
465
+ devicesInHB.forEach(accessory => this.removeAccessory(accessory))
466
+ throw new Error(this.lang.missingCreds)
467
+ }
468
+
469
+ // Require any libraries that the accessory instances use
470
+ this.cusChar = new platformChars(this.api)
471
+ this.eveChar = new eveChars(this.api)
472
+ this.eveService = eveService(this.api)
473
+
474
+ // Persist files are used to store device info that could be used for LAN only mode
475
+ try {
476
+ this.storageLAN = storage.create({
477
+ dir: join(this.api.user.persistPath(), '/../homebridge-ewelink'),
478
+ forgiveParseErrors: true,
479
+ })
480
+ await this.storageLAN.init()
481
+ this.storageClientLAN = true
482
+ } catch (err) {
483
+ this.log.debugWarn(`${this.lang.storageSetupErr} ${parseError(err)}`)
484
+ }
485
+
486
+ // Persist files are used to store device info that can be used by my other plugins
487
+ try {
488
+ this.storageData = storage.create({
489
+ dir: join(this.api.user.persistPath(), '/../bwp91_cache'),
490
+ forgiveParseErrors: true,
491
+ })
492
+ await this.storageData.init()
493
+ this.storageClientData = true
494
+ } catch (err) {
495
+ this.log.debugWarn(`${this.lang.storageSetupErr} ${parseError(err)}`)
496
+ }
497
+
498
+ // Manually disable no response mode if mode is set to lan
499
+ if (this.config.mode === 'lan') {
500
+ this.config.disableNoResponse = true
501
+ }
502
+
503
+ const deviceList = []
504
+ const groupList = []
505
+
506
+ // Username and password are optional
507
+ if (this.config.username && this.config.password) {
508
+ // Set up the HTTP client, get the user HTTP host, and login
509
+ this.httpClient = new httpClient(this)
510
+ const authData = await this.httpClient.login()
511
+ this.config.password = authData.password
512
+
513
+ // Get a home and device list via HTTP request
514
+ await this.httpClient.getHomes()
515
+ const { httpDeviceList, httpGroupList } = await this.httpClient.getDevices()
516
+ httpDeviceList.forEach(device => deviceList.push(device))
517
+ httpGroupList.forEach(group => groupList.push(group))
518
+
519
+ // Set up the WS client, get the user WS host and login
520
+ if (this.config.mode !== 'lan') {
521
+ this.wsClient = new wsClient(this, authData)
522
+ await this.wsClient.login()
523
+
524
+ // Refresh the WS connection every 60 minutes
525
+ this.wsRefresh = setInterval(async () => {
526
+ try {
527
+ this.log.debug(this.lang.wsRef)
528
+ await this.wsClient.login()
529
+ } catch (err) {
530
+ this.log.warn('%s %s.', this.lang.wsRefFail, parseError(err))
531
+ }
532
+ }, 3600000)
533
+ }
534
+
535
+ // Clear the storage folder and start again when we have access to http devices
536
+ if (this.storageClientLAN) {
537
+ try {
538
+ await this.storageLAN.clear()
539
+ } catch (err) {
540
+ this.log.debugWarn(`${this.lang.storageClearErr} ${parseError(err)}`)
541
+ }
542
+ }
543
+ } else {
544
+ // Warn that HTTP and WS are disabled
545
+ this.log.warn('%s %s.', this.lang.httpDisabled, this.lang.missingCreds)
546
+
547
+ // Get the persisted device data if we are in lan only mode
548
+ if (this.config.mode === 'lan' && this.storageClientLAN) {
549
+ try {
550
+ this.log('Obtaining device list from storage.')
551
+ const persistDeviceList = await this.storageLAN.values()
552
+ persistDeviceList.forEach(device => deviceList.push(device))
553
+ } catch (err) {
554
+ this.log.debugWarn(`${this.lang.storageReadErr} ${parseError(err)}`)
555
+ }
556
+ }
557
+ }
558
+
559
+ // Set up the LAN client, scan for device and start monitoring
560
+ if (this.config.mode !== 'wan') {
561
+ this.lanClient = new lanClient(this)
562
+ this.lanDevices = await this.lanClient.getHosts()
563
+ await this.lanClient.startMonitor()
564
+ }
565
+
566
+ // Initialise each device into HB
567
+ deviceList.forEach(device => this.initialiseDevice(device))
568
+ groupList.forEach(async (group) => {
569
+ // Create the format of a device
570
+ group.extra = { uiid: 5000 }
571
+ group.deviceid = group.id
572
+ group.productModel = `Group [${group.uiid}]`
573
+ group.brandName = 'eWeLink'
574
+ group.online = true
575
+ await this.initialiseDevice(group)
576
+ })
577
+
578
+ // Check for redundant accessories (in HB but not eWeLink)
579
+ devicesInHB.forEach(async (accessory) => {
580
+ if (
581
+ !deviceList.some(el => el.deviceid === accessory.context.eweDeviceId)
582
+ && !groupList.some(el => el.id === accessory.context.eweDeviceId)
583
+ ) {
584
+ this.removeAccessory(accessory)
585
+ }
586
+ })
587
+
588
+ // Set up the LAN listener for device notifications
589
+ if (this.lanClient) {
590
+ this.lanClient.receiveUpdate(device => this.receiveDeviceUpdate(device))
591
+ }
592
+
593
+ // Set up the WS listener for device notifications
594
+ if (this.wsClient) {
595
+ this.wsClient.receiveUpdate(device => this.receiveDeviceUpdate(device))
596
+ }
597
+
598
+ // Set up the listener server for the API if the user has this enabled
599
+ if (this.config.apiPort !== 0 && this.config.password) {
600
+ this.apiClient = new apiClient(this, devicesInHB)
601
+ this.apiServer = createServer(async (req, res) => {
602
+ // The 'homepage' shows a html document with info about the API
603
+ if (req.url === '/') {
604
+ res.writeHead(200, { 'Content-Type': 'text/html' })
605
+ res.end(this.apiClient.showHome())
606
+ return
607
+ }
608
+
609
+ // Request is not for the homepage so action appropriately
610
+ res.writeHead(200, { 'Content-Type': 'application/json' })
611
+ try {
612
+ const response = await this.apiClient.action(req)
613
+
614
+ // Actioning the request was successful so respond with a success
615
+ res.end(JSON.stringify({ success: true, response }))
616
+ } catch (err) {
617
+ // An error occurred actioning the request so respond with the error
618
+ res.end(JSON.stringify({ success: false, error: `${err.message}.` }))
619
+ }
620
+ })
621
+
622
+ // Start listening on the above created server
623
+ this.apiServer.listen(this.config.apiPort === 1 ? 0 : this.config.apiPort, (err) => {
624
+ if (err) {
625
+ this.log.warn('%s [%s].', this.lang.apiListenErr, err)
626
+ } else {
627
+ this.log('%s [%s].', this.lang.apiListening, this.apiServer.address().port)
628
+ }
629
+ })
630
+ }
631
+
632
+ // Setup successful
633
+ this.log('%s. %s', this.lang.complete, this.lang.welcome)
634
+ } catch (err) {
635
+ // Catch any errors during setup
636
+ const eText = parseError(err, [
637
+ this.lang.missingCreds,
638
+ 'password error! [10001]',
639
+ 'user does not exists [10003]',
640
+ ])
641
+ this.log.warn('***** %s. *****', this.lang.disabling)
642
+ this.log.warn('***** %s. *****', eText)
643
+ this.pluginShutdown()
644
+ }
645
+ }
646
+
647
+ pluginShutdown() {
648
+ // A function that is called when the plugin fails to load or Homebridge restarts
649
+ try {
650
+ // Shutdown the listener server if it's running
651
+ if (this.apiServer) {
652
+ this.apiServer.close(() => {
653
+ this.log.debug(this.lang.apiShutdown)
654
+ })
655
+ }
656
+
657
+ // Stop the LAN monitoring
658
+ if (this.lanClient) {
659
+ this.lanClient.closeConnection()
660
+ }
661
+
662
+ // Close the WS connection
663
+ if (this.wsClient) {
664
+ clearInterval(this.wsRefresh)
665
+ this.wsClient.closeConnection()
666
+ }
667
+ } catch (err) {
668
+ // No need to show errors at this point
669
+ }
670
+ }
671
+
672
+ applyAccessoryLogging(accessory) {
673
+ if (this.isBeta) {
674
+ accessory.log = msg => this.log('[%s] %s.', accessory.displayName, msg)
675
+ accessory.logWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg)
676
+ accessory.logDebug = msg => this.log('[%s] %s.', accessory.displayName, msg)
677
+ accessory.logDebugWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg)
678
+ } else {
679
+ if (this.config.disableDeviceLogging) {
680
+ accessory.log = () => {}
681
+ accessory.logWarn = () => {}
682
+ } else {
683
+ accessory.log = msg => this.log('[%s] %s.', accessory.displayName, msg)
684
+ accessory.logWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg)
685
+ }
686
+ accessory.logDebug = () => {}
687
+ accessory.logDebugWarn = () => {}
688
+ }
689
+ }
690
+
691
+ async initialiseDevice(device) {
692
+ try {
693
+ let accessory
694
+ const uiid = device?.extra?.uiid || 0
695
+ const uuid = this.api.hap.uuid.generate(`${device.deviceid}SWX`)
696
+ device.showAs = this.deviceConf?.[device.deviceid]?.showAs || 'default'
697
+
698
+ // Remove old sub accessories for Accessory Simulations and DUALR3 in motor/meter mode
699
+ if (
700
+ device.showAs !== 'default'
701
+ || (platformConsts.devices.switchMultiPower.includes(uiid)
702
+ && [2, 3].includes(device.params.workMode))
703
+ ) {
704
+ for (let i = 0; i <= 4; i += 1) {
705
+ const uuidsub = this.api.hap.uuid.generate(`${device.deviceid}SW${i}`)
706
+ if (devicesInHB.has(uuidsub)) {
707
+ this.removeAccessory(devicesInHB.get(uuidsub))
708
+ }
709
+ }
710
+ }
711
+
712
+ // Set up the correct instance for this particular device
713
+ if (platformConsts.devices.switchMultiPower.includes(uiid) && device.params.workMode === 2) {
714
+ /**
715
+ ***********************
716
+ BLINDS [DUALR3 MOTOR MODE]
717
+ ************************
718
+ */
719
+ // Check the device has been calibrated
720
+ if (device.params.calibState === 0) {
721
+ if (devicesInHB.has(uuid)) {
722
+ this.removeAccessory(devicesInHB.get(uuid))
723
+ }
724
+ this.log.warn('[%s] %s.', device.name, this.lang.dualr3NoCalib)
725
+ this.ignoredDevices.push(device.deviceid)
726
+ return
727
+ }
728
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
729
+ this.applyAccessoryLogging(accessory)
730
+ accessory.control = new deviceTypes.deviceMotor(this, accessory)
731
+ /** */
732
+ } else if (
733
+ platformConsts.devices.switchMultiPower.includes(uiid)
734
+ && device.params.workMode === 3
735
+ ) {
736
+ /**
737
+ ***********************
738
+ BLINDS [DUALR3 METER MODE]
739
+ ************************
740
+ */
741
+ if (devicesInHB.has(uuid)) {
742
+ this.removeAccessory(devicesInHB.get(uuid))
743
+ }
744
+ this.log.warn('[%s] %s.', device.name, this.lang.dualr3NoMeter)
745
+ this.ignoredDevices.push(device.deviceid)
746
+ return
747
+ } else if (platformConsts.devices.panel.includes(uiid)) {
748
+ /**
749
+ ****
750
+ NSPANEL
751
+ *****
752
+ */
753
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
754
+ this.applyAccessoryLogging(accessory)
755
+ accessory.control = new deviceTypes.devicePanel(this, accessory)
756
+ /** */
757
+ } else if (platformConsts.devices.curtain.includes(uiid)) {
758
+ /**
759
+ **************************
760
+ BLINDS [EWELINK UIID 11 & 67]
761
+ ***************************
762
+ */
763
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
764
+ this.applyAccessoryLogging(accessory)
765
+ accessory.control = new deviceTypes.deviceCurtain(this, accessory)
766
+ /** */
767
+ } else if (device.showAs === 'blind') {
768
+ /**
769
+ **************************
770
+ BLINDS [ACCESSORY SIMULATION]
771
+ ***************************
772
+ */
773
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
774
+ this.applyAccessoryLogging(accessory)
775
+ accessory.control = new deviceTypes.sim.deviceBlind(this, accessory)
776
+ /** */
777
+ } else if (device.showAs === 'door') {
778
+ /**
779
+ *************************
780
+ DOORS [ACCESSORY SIMULATION]
781
+ **************************
782
+ */
783
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
784
+ this.applyAccessoryLogging(accessory)
785
+ accessory.control = new deviceTypes.sim.deviceDoor(this, accessory)
786
+ /** */
787
+ } else if (device.showAs === 'window') {
788
+ /**
789
+ ***************************
790
+ WINDOWS [ACCESSORY SIMULATION]
791
+ ****************************
792
+ */
793
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
794
+ this.applyAccessoryLogging(accessory)
795
+ accessory.control = new deviceTypes.sim.deviceWindow(this, accessory)
796
+ /** */
797
+ } else if (this.obstructSwitches[device.deviceid]) {
798
+ /**
799
+ ***************************
800
+ OBSTRUCTION DETECTION SWITCHES
801
+ ****************************
802
+ */
803
+ accessory = this.addAccessory(device, `${device.deviceid}SWX`, true)
804
+ this.applyAccessoryLogging(accessory)
805
+ accessory.control = new deviceTypes.sim.deviceGarageOdSwitch(this, accessory, devicesInHB)
806
+ /** */
807
+ } else if (
808
+ Object.values(this.deviceConf).some(el => el.sensorId === device.deviceid)
809
+ && (platformConsts.devices.garageSensors.includes(uiid) || device.showAs === 'sensor')
810
+ ) {
811
+ /**
812
+ ***************************************
813
+ SENSORS [AS GARAGE/LOCK SENSOR SIMULATION]
814
+ ****************************************
815
+ */
816
+ const sim = Object.entries(this.deviceConf).find(
817
+ ([, el]) => el.sensorId === device.deviceid && ['garage', 'lock'].includes(el.showAs),
818
+ )
819
+ const uuidSub = this.api.hap.uuid.generate(`${sim[0]}SWX`)
820
+ if (devicesInHB.has(uuidSub)) {
821
+ const subAccessory = devicesInHB.get(uuidSub)
822
+ let instance
823
+ if (sim[1].hideSensor) {
824
+ // If the sensor exists in Homebridge then remove it as needs to be re-added as hidden
825
+ if (devicesInHB.has(uuid)) {
826
+ this.removeAccessory(devicesInHB.get(uuid))
827
+ }
828
+ instance = deviceTypes.sim.deviceSensorHidden
829
+ accessory = this.addAccessory(device, `${device.deviceid}SWX`, true)
830
+ } else {
831
+ instance = deviceTypes.sim.deviceSensorVisible
832
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
833
+ }
834
+ this.applyAccessoryLogging(accessory)
835
+ accessory.control = new instance(this, accessory, subAccessory)
836
+ } else {
837
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
838
+ this.applyAccessoryLogging(accessory)
839
+ accessory.logWarn(this.lang.sensorNoDevice)
840
+ accessory.control = new deviceTypes.deviceSensorContact(this, accessory)
841
+ }
842
+ /** */
843
+ } else if (device.showAs === 'garage') {
844
+ /**
845
+ **************************************
846
+ GARAGE DOORS [ONE] [ACCESSORY SIMULATION]
847
+ ***************************************
848
+ */
849
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
850
+ this.applyAccessoryLogging(accessory)
851
+ accessory.control = new deviceTypes.sim.deviceGarageOne(this, accessory)
852
+ /** */
853
+ } else if (device.showAs === 'garage_two') {
854
+ /**
855
+ **************************************
856
+ GARAGE DOORS [TWO] [ACCESSORY SIMULATION]
857
+ ***************************************
858
+ */
859
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
860
+ this.applyAccessoryLogging(accessory)
861
+ accessory.control = new deviceTypes.sim.deviceGarageTwo(this, accessory)
862
+ /** */
863
+ } else if (device.showAs === 'garage_four') {
864
+ /**
865
+ ***************************************
866
+ GARAGE DOORS [FOUR] [ACCESSORY SIMULATION]
867
+ ****************************************
868
+ */
869
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
870
+ this.applyAccessoryLogging(accessory)
871
+ accessory.control = new deviceTypes.sim.deviceGarageFour(this, accessory)
872
+ /** */
873
+ } else if (device.showAs === 'garage_eachen') {
874
+ /**
875
+ *************************
876
+ GARAGE DOORS [EACHEN GD-DC5]
877
+ **************************
878
+ */
879
+ const instance = this.deviceConf[device.deviceid]
880
+ && this.deviceConf[device.deviceid].showAsEachen === 'lock'
881
+ ? deviceTypes.sim.deviceLockEachen
882
+ : deviceTypes.sim.deviceGarageEachen
883
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
884
+ this.applyAccessoryLogging(accessory)
885
+ accessory.control = new instance(this, accessory)
886
+ /** */
887
+ } else if (device.showAs === 'gate') {
888
+ /**
889
+ ******************************************
890
+ GATES (AS GARAGE DOOR) [ACCESSORY SIMULATION]
891
+ *******************************************
892
+ */
893
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
894
+ this.applyAccessoryLogging(accessory)
895
+ accessory.control = new deviceTypes.sim.deviceGateOne(this, accessory, devicesInHB)
896
+ /** */
897
+ } else if (device.showAs === 'lock') {
898
+ /**
899
+ *************************
900
+ LOCKS [ACCESSORY SIMULATION]
901
+ **************************
902
+ */
903
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
904
+ this.applyAccessoryLogging(accessory)
905
+ accessory.control = new deviceTypes.sim.deviceLockOne(this, accessory)
906
+ /** */
907
+ } else if (device.showAs === 'switch_valve') {
908
+ /**
909
+ ********************************
910
+ SWITCH-VALVE [ACCESSORY SIMULATION]
911
+ *********************************
912
+ */
913
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
914
+ this.applyAccessoryLogging(accessory)
915
+ accessory.control = new deviceTypes.sim.deviceSwitchValve(this, accessory)
916
+ /** */
917
+ } else if (device.showAs === 'tap') {
918
+ /**
919
+ ******************************
920
+ TAPS [ONE] [ACCESSORY SIMULATION]
921
+ *******************************
922
+ */
923
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
924
+ this.applyAccessoryLogging(accessory)
925
+ accessory.control = new deviceTypes.sim.deviceTapOne(this, accessory)
926
+ /** */
927
+ } else if (device.showAs === 'tap_two') {
928
+ /**
929
+ ******************************
930
+ TAPS [TWO] [ACCESSORY SIMULATION]
931
+ *******************************
932
+ */
933
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
934
+ this.applyAccessoryLogging(accessory)
935
+ accessory.control = new deviceTypes.sim.deviceTapTwo(this, accessory)
936
+ /** */
937
+ } else if (device.showAs === 'valve') {
938
+ /**
939
+ ********************************
940
+ VALVES [ONE] [ACCESSORY SIMULATION]
941
+ *********************************
942
+ */
943
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
944
+ this.applyAccessoryLogging(accessory)
945
+ accessory.control = new deviceTypes.sim.deviceValveOne(this, accessory)
946
+ /** */
947
+ } else if (device.showAs === 'valve_two') {
948
+ /**
949
+ ********************************
950
+ VALVES [TWO] [ACCESSORY SIMULATION]
951
+ *********************************
952
+ */
953
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
954
+ this.applyAccessoryLogging(accessory)
955
+ accessory.control = new deviceTypes.sim.deviceValveTwo(this, accessory)
956
+ /** */
957
+ } else if (device.showAs === 'valve_four') {
958
+ /**
959
+ *********************************
960
+ VALVES [FOUR] [ACCESSORY SIMULATION]
961
+ **********************************
962
+ */
963
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
964
+ this.applyAccessoryLogging(accessory)
965
+ accessory.control = new deviceTypes.sim.deviceValveFour(this, accessory)
966
+ /** */
967
+ } else if (device.showAs === 'sensor') {
968
+ /**
969
+ *****************
970
+ SENSORS [SIMULATION]
971
+ ******************
972
+ */
973
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
974
+ this.applyAccessoryLogging(accessory)
975
+ accessory.control = new deviceTypes.sim.deviceSensor(this, accessory)
976
+ /** */
977
+ } else if (device.showAs === 'p_button') {
978
+ /**
979
+ *****************************
980
+ PROGRAMMABLE BUTTON [SIMULATION]
981
+ ******************************
982
+ */
983
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
984
+ this.applyAccessoryLogging(accessory)
985
+ accessory.control = new deviceTypes.sim.devicePButton(this, accessory)
986
+ /** */
987
+ } else if (device.showAs === 'doorbell') {
988
+ /**
989
+ ******************
990
+ DOORBELL [SIMULATION]
991
+ *******************
992
+ */
993
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
994
+ this.applyAccessoryLogging(accessory)
995
+ accessory.control = new deviceTypes.sim.deviceDoorbell(this, accessory)
996
+ /** */
997
+ } else if (device.showAs === 'purifier') {
998
+ /**
999
+ *******************
1000
+ PURIFIERS [SIMULATION]
1001
+ ********************
1002
+ */
1003
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1004
+ this.applyAccessoryLogging(accessory)
1005
+ accessory.control = new deviceTypes.sim.devicePurifier(this, accessory)
1006
+ /** */
1007
+ } else if (device.showAs === 'audio') {
1008
+ /**
1009
+ *************************
1010
+ AUDIO RECEIVERS [SIMULATION]
1011
+ **************************
1012
+ */
1013
+ if (devicesInHB.has(uuid)) {
1014
+ this.removeAccessory(devicesInHB.get(uuid))
1015
+ }
1016
+ accessory = this.addExternalAccessory(device, `${device.deviceid}SWX`, 34)
1017
+ this.applyAccessoryLogging(accessory)
1018
+ accessory.control = new deviceTypes.sim.deviceTv(this, accessory)
1019
+ /** */
1020
+ } else if (device.showAs === 'box') {
1021
+ /**
1022
+ *********************
1023
+ SET-TOP BOX [SIMULATION]
1024
+ **********************
1025
+ */
1026
+ if (devicesInHB.has(uuid)) {
1027
+ this.removeAccessory(devicesInHB.get(uuid))
1028
+ }
1029
+ accessory = this.addExternalAccessory(device, `${device.deviceid}SWX`, 35)
1030
+ this.applyAccessoryLogging(accessory)
1031
+ accessory.control = new deviceTypes.sim.deviceTv(this, accessory)
1032
+ /** */
1033
+ } else if (device.showAs === 'stick') {
1034
+ /**
1035
+ *************************
1036
+ STREAMING STICK [SIMULATION]
1037
+ **************************
1038
+ */
1039
+ if (devicesInHB.has(uuid)) {
1040
+ this.removeAccessory(devicesInHB.get(uuid))
1041
+ }
1042
+ accessory = this.addExternalAccessory(device, `${device.deviceid}SWX`, 36)
1043
+ this.applyAccessoryLogging(accessory)
1044
+ accessory.control = new deviceTypes.sim.deviceTv(this, accessory)
1045
+ /** */
1046
+ } else if (
1047
+ device.showAs === 'sensor_leak'
1048
+ && platformConsts.devices.sensorContact.includes(uiid)
1049
+ ) {
1050
+ /**
1051
+ ************************************
1052
+ SENSORS [LEAK DW2 ACCESSORY SIMULATION]
1053
+ *************************************
1054
+ */
1055
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1056
+ this.applyAccessoryLogging(accessory)
1057
+ accessory.control = new deviceTypes.sim.deviceSensorLeak(this, accessory)
1058
+ /** */
1059
+ } else if (platformConsts.devices.sensorContact.includes(uiid)) {
1060
+ /**
1061
+ *****************
1062
+ SENSORS [SONOFF DW2]
1063
+ ******************
1064
+ */
1065
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1066
+ this.applyAccessoryLogging(accessory)
1067
+ accessory.control = new deviceTypes.deviceSensorContact(this, accessory)
1068
+ } else if (platformConsts.devices.fan.includes(uiid)) {
1069
+ /**
1070
+ FANS
1071
+ *
1072
+ */
1073
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1074
+ this.applyAccessoryLogging(accessory)
1075
+ accessory.control = new deviceTypes.deviceFan(this, accessory)
1076
+ /**/
1077
+ } else if (platformConsts.devices.diffuser.includes(uiid)) {
1078
+ /**
1079
+ ******
1080
+ DIFFUSERS
1081
+ *******
1082
+ */
1083
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1084
+ this.applyAccessoryLogging(accessory)
1085
+ accessory.control = new deviceTypes.deviceDiffuser(this, accessory)
1086
+ /** */
1087
+ } else if (platformConsts.devices.humidifier.includes(uiid)) {
1088
+ /**
1089
+ ********
1090
+ HUMIDIFIERS
1091
+ *********
1092
+ */
1093
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1094
+ this.applyAccessoryLogging(accessory)
1095
+ accessory.control = new deviceTypes.deviceHumidifier(this, accessory)
1096
+ /** */
1097
+ } else if (platformConsts.devices.thermostat.includes(uiid)) {
1098
+ /**
1099
+ ********
1100
+ THERMOSTATS
1101
+ *********
1102
+ */
1103
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1104
+ this.applyAccessoryLogging(accessory)
1105
+ accessory.control = new deviceTypes.deviceThermostat(this, accessory)
1106
+ /** */
1107
+ } else if (
1108
+ device.showAs === 'thermostat'
1109
+ && platformConsts.devices.sensorAmbient.includes(uiid)
1110
+ ) {
1111
+ /**
1112
+ ***************************************
1113
+ THERMOSTATS [TH10/16 ACCESSORY SIMULATION]
1114
+ ****************************************
1115
+ */
1116
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1117
+ accessory.context.sensorType = device.params.sensorType
1118
+ this.applyAccessoryLogging(accessory)
1119
+ accessory.control = new deviceTypes.sim.deviceThThermostat(this, accessory)
1120
+ /** */
1121
+ } else if (device.showAs === 'heater' && platformConsts.devices.sensorAmbient.includes(uiid)) {
1122
+ /**
1123
+ ***********************************
1124
+ HEATERS [TH10/16 ACCESSORY SIMULATION]
1125
+ ************************************
1126
+ */
1127
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1128
+ accessory.context.sensorType = device.params.sensorType
1129
+ this.applyAccessoryLogging(accessory)
1130
+ accessory.control = new deviceTypes.sim.deviceThHeater(this, accessory)
1131
+ /** */
1132
+ } else if (device.showAs === 'cooler' && platformConsts.devices.sensorAmbient.includes(uiid)) {
1133
+ /**
1134
+ ***********************************
1135
+ COOLERS [TH10/16 ACCESSORY SIMULATION]
1136
+ ************************************
1137
+ */
1138
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1139
+ accessory.context.sensorType = device.params.sensorType
1140
+ this.applyAccessoryLogging(accessory)
1141
+ accessory.control = new deviceTypes.sim.deviceThCooler(this, accessory)
1142
+ /** */
1143
+ } else if (
1144
+ device.showAs === 'humidifier'
1145
+ && platformConsts.devices.sensorAmbient.includes(uiid)
1146
+ ) {
1147
+ /**
1148
+ ***************************************
1149
+ HUMIDIFIERS [TH10/16 ACCESSORY SIMULATION]
1150
+ ****************************************
1151
+ */
1152
+ if (device.params.sensorType === 'DS18B20') {
1153
+ if (devicesInHB.has(uuid)) {
1154
+ this.removeAccessory(devicesInHB.get(uuid))
1155
+ }
1156
+ this.log.warn('[%s] %s.', device.name, this.lang.sensorErr)
1157
+ return
1158
+ }
1159
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1160
+ accessory.context.sensorType = device.params.sensorType
1161
+ this.applyAccessoryLogging(accessory)
1162
+ accessory.control = new deviceTypes.sim.deviceThHumidifier(this, accessory)
1163
+ /** */
1164
+ } else if (
1165
+ device.showAs === 'dehumidifier'
1166
+ && platformConsts.devices.sensorAmbient.includes(uiid)
1167
+ ) {
1168
+ /**
1169
+ *****************************************
1170
+ DEHUMIDIFIERS [TH10/16 ACCESSORY SIMULATION]
1171
+ ******************************************
1172
+ */
1173
+ if (device.params.sensorType === 'DS18B20') {
1174
+ this.log.warn('[%s] %s.', device.name, this.lang.sensorErr)
1175
+ return
1176
+ }
1177
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1178
+ accessory.context.sensorType = device.params.sensorType
1179
+ this.applyAccessoryLogging(accessory)
1180
+ accessory.control = new deviceTypes.sim.deviceThDehumidifier(this, accessory)
1181
+ /** */
1182
+ } else if (platformConsts.devices.sensorAmbient.includes(uiid)) {
1183
+ /**
1184
+ *********************
1185
+ SENSOR [AMBIENT-TH10/16]
1186
+ **********************
1187
+ */
1188
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1189
+ accessory.context.sensorType = device.params.sensorType
1190
+ this.applyAccessoryLogging(accessory)
1191
+ accessory.control = new deviceTypes.deviceSensorAmbient(this, accessory)
1192
+ /** */
1193
+ } else if (platformConsts.devices.sensorTempHumi.includes(uiid)) {
1194
+ /**
1195
+ ***********************
1196
+ SENSOR [AMBIENT-SONOFF SC]
1197
+ ************************
1198
+ */
1199
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1200
+ this.applyAccessoryLogging(accessory)
1201
+ accessory.control = new deviceTypes.deviceSensorTempHumi(this, accessory)
1202
+ /** */
1203
+ } else if (device.showAs === 'heater') {
1204
+ /**
1205
+ *****************
1206
+ HEATERS [SIMULATION]
1207
+ ******************
1208
+ */
1209
+ if (!this.deviceConf[device.deviceid].temperatureSource) {
1210
+ this.log.warn('[%s] %s.', device.name, this.lang.heaterSimNoSensor)
1211
+ if (devicesInHB.has(uuid)) {
1212
+ this.removeAccessory(devicesInHB.get(uuid))
1213
+ }
1214
+ return
1215
+ }
1216
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1217
+ this.applyAccessoryLogging(accessory)
1218
+ accessory.control = new deviceTypes.sim.deviceHeater(this, accessory)
1219
+ /** */
1220
+ } else if (device.showAs === 'cooler') {
1221
+ /**
1222
+ *****************
1223
+ COOLERS [SIMULATION]
1224
+ ******************
1225
+ */
1226
+ if (!this.deviceConf[device.deviceid].temperatureSource) {
1227
+ this.log.warn('[%s] %s.', device.name, this.lang.heaterSimNoSensor)
1228
+ if (devicesInHB.has(uuid)) {
1229
+ this.removeAccessory(devicesInHB.get(uuid))
1230
+ }
1231
+ return
1232
+ }
1233
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1234
+ this.applyAccessoryLogging(accessory)
1235
+ accessory.control = new deviceTypes.sim.deviceCooler(this, accessory)
1236
+ /** */
1237
+ } else if (platformConsts.devices.lightRGB.includes(uiid)) {
1238
+ /**
1239
+ *********
1240
+ LIGHTS [RGB]
1241
+ **********
1242
+ */
1243
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1244
+ this.applyAccessoryLogging(accessory)
1245
+ accessory.control = new deviceTypes.deviceLightRGB(this, accessory)
1246
+ /** */
1247
+ } else if (platformConsts.devices.lightCCT.includes(uiid)) {
1248
+ /**
1249
+ *********
1250
+ LIGHTS [CCT]
1251
+ **********
1252
+ */
1253
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1254
+ this.applyAccessoryLogging(accessory)
1255
+ accessory.control = new deviceTypes.deviceLightCCT(this, accessory)
1256
+ /** */
1257
+ } else if (platformConsts.devices.lightRGBCCT.includes(uiid)) {
1258
+ /**
1259
+ ***************
1260
+ LIGHTS [RGB & CCT]
1261
+ ****************
1262
+ */
1263
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1264
+ this.applyAccessoryLogging(accessory)
1265
+ accessory.control = new deviceTypes.deviceLightRGBCCT(this, accessory)
1266
+ /** */
1267
+ } else if (platformConsts.devices.lightDimmer.includes(uiid)) {
1268
+ /**
1269
+ ************
1270
+ LIGHTS [DIMMER]
1271
+ *************
1272
+ */
1273
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1274
+ this.applyAccessoryLogging(accessory)
1275
+ accessory.control = device.showAs === 'fan'
1276
+ ? new deviceTypes.sim.deviceLightFan(this, accessory)
1277
+ : new deviceTypes.deviceLightDimmer(this, accessory)
1278
+ /** */
1279
+ /** */
1280
+ } else if (
1281
+ platformConsts.devices.switchSingle.includes(uiid)
1282
+ || platformConsts.devices.switchSinglePower.includes(uiid)
1283
+ || platformConsts.devices.switchSCM.includes(uiid)
1284
+ || platformConsts.devices.switchSCMPower.includes(uiid)
1285
+ ) {
1286
+ /**
1287
+ ******************************
1288
+ SWITCHES/OUTLETS [SINGLE CHANNEL]
1289
+ *******************************
1290
+ */
1291
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1292
+ this.applyAccessoryLogging(accessory)
1293
+ if (device.showAs === 'outlet') {
1294
+ accessory.control = this.deviceConf?.[device.deviceid]?.isInched
1295
+ ? new deviceTypes.deviceOutletSingleInched(this, accessory)
1296
+ : new deviceTypes.deviceOutletSingle(this, accessory)
1297
+ } else {
1298
+ accessory.control = this.deviceConf?.[device.deviceid]?.isInched
1299
+ ? new deviceTypes.deviceSwitchSingleInched(this, accessory)
1300
+ : new deviceTypes.deviceSwitchSingle(this, accessory)
1301
+ }
1302
+ /** */
1303
+ } else if (
1304
+ platformConsts.devices.switchMulti.includes(uiid)
1305
+ || platformConsts.devices.switchMultiPower.includes(uiid)
1306
+ ) {
1307
+ /**
1308
+ *****************************
1309
+ SWITCHES/OUTLETS [MULTI CHANNEL]
1310
+ ******************************
1311
+ */
1312
+
1313
+ // If a single accessory is leftover from a simulation then remove it
1314
+ if (devicesInHB.has(uuid)) {
1315
+ this.removeAccessory(devicesInHB.get(uuid))
1316
+ }
1317
+
1318
+ // Loop through the channels of the device
1319
+ for (let i = 0; i <= platformConsts.supportedDevices[uiid]; i += 1) {
1320
+ let subAccessory
1321
+ const uuidsub = this.api.hap.uuid.generate(`${device.deviceid}SW${i}`)
1322
+
1323
+ // Check if the user has chosen to hide any channels for this device
1324
+ if (
1325
+ this.hideChannels.includes(`${device.deviceid}SW${i}`)
1326
+ || (i === 0 && this.deviceConf?.[device.deviceid]?.inchChannels)
1327
+ ) {
1328
+ // The user has hidden this channel so if it exists then remove it
1329
+ if (devicesInHB.has(uuidsub)) {
1330
+ this.removeAccessory(devicesInHB.get(uuidsub))
1331
+ }
1332
+
1333
+ // If this is the main channel then add it to the array of hidden masters
1334
+ if (i === 0) {
1335
+ this.hideMasters.push(device.deviceid)
1336
+ }
1337
+
1338
+ // Add the sub accessory, but hidden, to Homebridge
1339
+ subAccessory = this.addAccessory(device, `${device.deviceid}SW${i}`, true)
1340
+ } else {
1341
+ // The user has not hidden this channel
1342
+ subAccessory = devicesInHB.get(uuidsub) || this.addAccessory(device, `${device.deviceid}SW${i}`)
1343
+ }
1344
+
1345
+ // Add context information to the sub accessory
1346
+ subAccessory.context.firmware = device.params.fwVersion || plugin.version
1347
+ subAccessory.context.reachableWAN = this.wsClient && device.online
1348
+ subAccessory.context.reachableLAN = this.lanClient && this.lanDevices.has(device.deviceid) && this.lanDevices.get(device.deviceid).ip
1349
+ subAccessory.context.eweBrandName = device.brandName
1350
+ subAccessory.context.eweBrandLogo = device.brandLogo
1351
+ subAccessory.context.eweShared = device?.sharedBy?.email || false
1352
+ subAccessory.context.ip = this.lanClient && subAccessory.context.reachableLAN
1353
+ ? this.lanDevices.get(device.deviceid).ip
1354
+ : false
1355
+ subAccessory.context.macAddress = device.extra?.staMac?.replace(/:+/g, '').replace(/..\B/g, '$&:') || false
1356
+ subAccessory.context.lanKey = device.devicekey
1357
+ this.applyAccessoryLogging(subAccessory)
1358
+ subAccessory.control = device.showAs === 'outlet'
1359
+ ? new deviceTypes.deviceOutletMulti(this, subAccessory, devicesInHB)
1360
+ : new deviceTypes.deviceSwitchMulti(this, subAccessory, devicesInHB)
1361
+
1362
+ // Mark the online/offline status of certain devices
1363
+ if (this.wsClient && subAccessory?.control?.markStatus) {
1364
+ subAccessory.control.markStatus(device.online)
1365
+ }
1366
+
1367
+ // Update any changes to the sub accessory to the platform
1368
+ this.api.updatePlatformAccessories([subAccessory])
1369
+ devicesInHB.set(subAccessory.UUID, subAccessory)
1370
+ if (i === 0) {
1371
+ accessory = subAccessory
1372
+ }
1373
+ }
1374
+ /** */
1375
+ } else if (platformConsts.devices.switchMate.includes(uiid)) {
1376
+ /**
1377
+ *****************
1378
+ SWITCH MATE (S-MATE)
1379
+ ******************
1380
+ */
1381
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1382
+ this.applyAccessoryLogging(accessory)
1383
+ accessory.control = new deviceTypes.deviceSwitchMate(this, accessory)
1384
+ /** */
1385
+ } else if (platformConsts.devices.switchMan.includes(uiid)) {
1386
+ /**
1387
+ *******
1388
+ SWITCH MAN
1389
+ ********
1390
+ */
1391
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1392
+ this.applyAccessoryLogging(accessory)
1393
+ accessory.control = new deviceTypes.deviceSwitchMan(this, accessory)
1394
+ /** */
1395
+ } else if (platformConsts.devices.rfBridge.includes(uiid)) {
1396
+ /**
1397
+ *******************
1398
+ RF BRIDGE + SUBDEVICES
1399
+ ********************
1400
+ */
1401
+ let rfChlCounter = 0
1402
+
1403
+ // Make an array of sub devices connected to the RF Bridge
1404
+ const rfMap = []
1405
+ if (device?.tags?.zyx_info) {
1406
+ device.tags.zyx_info.forEach(remote => rfMap.push({
1407
+ name: remote.name,
1408
+ type: remote.remote_type,
1409
+ buttons: Object.assign({}, ...remote.buttonName),
1410
+ }))
1411
+ }
1412
+ accessory = this.addAccessory(device, `${device.deviceid}SW0`, true, { rfMap }, 'rf_pri')
1413
+ this.applyAccessoryLogging(accessory)
1414
+ accessory.control = new deviceTypes.deviceRfBridge(this, accessory, devicesInHB)
1415
+
1416
+ // We don't want to add the main bridge as a visible accessory in Homebridge
1417
+ this.hideMasters.push(device.deviceid)
1418
+
1419
+ // Flag used for later to remove and re-add the subdevices if user configured
1420
+ const resetRF = this.deviceConf?.[device.deviceid]?.resetOnStartup
1421
+
1422
+ // Loop through each sub device connected to the RF Bridge
1423
+ rfMap.forEach((subDevice) => {
1424
+ const swNumber = rfChlCounter + 1
1425
+ let subAccessory
1426
+ let subType
1427
+ const fullDeviceId = `${device.deviceid}SW${swNumber}`
1428
+ const uuidsub = this.api.hap.uuid.generate(fullDeviceId)
1429
+ const deviceConf = this.rfSubdevices[fullDeviceId] || {}
1430
+
1431
+ // Check which eWeLink type the connected sub device is
1432
+ let subInstance
1433
+ switch (subDevice.type) {
1434
+ case '1':
1435
+ case '2':
1436
+ case '3':
1437
+ case '4':
1438
+ subType = 'button'
1439
+ subInstance = deviceTypes.deviceRfButton
1440
+ break
1441
+ case '5':
1442
+ switch (deviceConf.curtainType) {
1443
+ case 'blind':
1444
+ case 'door':
1445
+ case 'window':
1446
+ subType = deviceConf.curtainType
1447
+ subInstance = `simulation/rf-${deviceConf.curtainType}`
1448
+ break
1449
+ default:
1450
+ subType = 'curtain'
1451
+ subInstance = deviceTypes.deviceRfButton
1452
+ break
1453
+ }
1454
+ break
1455
+ case '6':
1456
+ case '7':
1457
+ subType = 'sensor'
1458
+ subInstance = deviceTypes.deviceRfSensor
1459
+ break
1460
+ default: {
1461
+ accessory.logWarn(`${this.lang.devNotSupYet}\n[${uiid}-${subDevice.type || '?'}] ${JSON.stringify(device.params)}`)
1462
+ return
1463
+ }
1464
+ }
1465
+
1466
+ // Create an object to save to the sub accessory context
1467
+ const subExtraContext = {
1468
+ buttons: subDevice.buttons,
1469
+ subType,
1470
+ swNumber,
1471
+ name: subDevice.name,
1472
+ }
1473
+
1474
+ // Check if we need to reset the RF devices
1475
+ subAccessory = devicesInHB.get(uuidsub)
1476
+ if (resetRF && subAccessory) {
1477
+ this.removeAccessory(subAccessory)
1478
+ }
1479
+
1480
+ // Get the sub accessory if it's new or hasn't been removed above
1481
+ subAccessory = devicesInHB.get(uuidsub)
1482
+ || this.addAccessory(device, fullDeviceId, false, subExtraContext, 'rf_sub')
1483
+
1484
+ // Add context information to the sub accessory
1485
+ subAccessory.context.firmware = device.params.fwVersion || plugin.version
1486
+ subAccessory.context.reachableWAN = this.wsClient && device.online
1487
+ subAccessory.context.reachableLAN = this.lanClient && this.lanDevices.has(device.deviceid) && this.lanDevices.get(device.deviceid).ip
1488
+ subAccessory.context.eweBrandName = device.brandName
1489
+ subAccessory.context.eweBrandLogo = device.brandLogo
1490
+ subAccessory.context.eweShared = device?.sharedBy?.email || false
1491
+ subAccessory.context.ip = this.lanClient && subAccessory.context.reachableLAN
1492
+ ? this.lanDevices.get(device.deviceid).ip
1493
+ : false
1494
+ subAccessory.context.macAddress = device.extra?.staMac?.replace(/:+/g, '').replace(/..\B/g, '$&:') || false
1495
+ subAccessory.context.lanKey = device.devicekey
1496
+ subAccessory.context.subType = subType
1497
+
1498
+ // Get the instance for this RF device
1499
+ this.applyAccessoryLogging(subAccessory)
1500
+ subAccessory.control = new subInstance(this, subAccessory)
1501
+
1502
+ // Mark the online/offline status of certain devices
1503
+ if (this.wsClient && subAccessory?.control?.markStatus) {
1504
+ subAccessory.control.markStatus(device.online)
1505
+ }
1506
+
1507
+ // Update any changes to the sub accessory to the platform
1508
+ this.api.updatePlatformAccessories([subAccessory])
1509
+ devicesInHB.set(subAccessory.UUID, subAccessory)
1510
+
1511
+ // Increment the counter
1512
+ rfChlCounter += Object.keys(subDevice.buttons || {}).length
1513
+ })
1514
+
1515
+ // Update any changes to the accessory to the platform
1516
+ accessory.context.channelCount = rfChlCounter
1517
+ /** */
1518
+ } else if (platformConsts.devices.zbBridge.includes(uiid)) {
1519
+ /**
1520
+ **********
1521
+ ZIGBEE BRIDGE
1522
+ ***********
1523
+ */
1524
+ this.ignoredDevices.push(device.deviceid)
1525
+ return
1526
+ /** */
1527
+ } else if (platformConsts.devices.zbSwitchStateless.includes(uiid)) {
1528
+ /**
1529
+ ********************
1530
+ ZIGBEE STATELESS SWITCH
1531
+ *********************
1532
+ */
1533
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1534
+ this.applyAccessoryLogging(accessory)
1535
+ accessory.control = new deviceTypes.zb.deviceSwitchStateless(this, accessory)
1536
+ /** */
1537
+ } else if (platformConsts.devices.zbLightDimmer.includes(uiid)) {
1538
+ /**
1539
+ ***************
1540
+ ZB LIGHTS [DIMMER]
1541
+ ****************
1542
+ */
1543
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1544
+ this.applyAccessoryLogging(accessory)
1545
+ accessory.control = new deviceTypes.zb.deviceLightDimmer(this, accessory)
1546
+ /** */
1547
+ } else if (platformConsts.devices.zbLightCCT.includes(uiid)) {
1548
+ /**
1549
+ ************
1550
+ ZB LIGHTS [CCT]
1551
+ *************
1552
+ */
1553
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1554
+ this.applyAccessoryLogging(accessory)
1555
+ accessory.control = new deviceTypes.zb.deviceLightCCT(this, accessory)
1556
+ /** */
1557
+ } else if (platformConsts.devices.zbLightRGBCCT.includes(uiid)) {
1558
+ /**
1559
+ ****************
1560
+ ZB LIGHTS [RGB+CCT]
1561
+ *****************
1562
+ */
1563
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1564
+ this.applyAccessoryLogging(accessory)
1565
+ accessory.control = new deviceTypes.zb.deviceLightRGBCCT(this, accessory)
1566
+ /** */
1567
+ } else if (platformConsts.devices.zbMotor.includes(uiid)) {
1568
+ /**
1569
+ *****
1570
+ ZB MOTOR
1571
+ ******
1572
+ */
1573
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1574
+ this.applyAccessoryLogging(accessory)
1575
+ accessory.control = new deviceTypes.zb.deviceMotor(this, accessory)
1576
+ /** */
1577
+ } else if (platformConsts.devices.zbSensorAmbient.includes(uiid)) {
1578
+ /**
1579
+ ****************
1580
+ ZB SENSOR [AMBIENT]
1581
+ *****************
1582
+ */
1583
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1584
+ this.applyAccessoryLogging(accessory)
1585
+ accessory.control = new deviceTypes.zb.deviceSensorAmbient(this, accessory)
1586
+ /** */
1587
+ } else if (platformConsts.devices.zbSensorMotion.includes(uiid)) {
1588
+ /**
1589
+ ***************
1590
+ ZB SENSOR [MOTION]
1591
+ ****************
1592
+ */
1593
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1594
+ this.applyAccessoryLogging(accessory)
1595
+ accessory.control = new deviceTypes.zb.deviceSensorMotion(this, accessory)
1596
+ /** */
1597
+ } else if (platformConsts.devices.zbSensorOccupancy.includes(uiid)) {
1598
+ /**
1599
+ ******************
1600
+ ZB SENSOR [OCCUPANCY]
1601
+ *******************
1602
+ */
1603
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1604
+ this.applyAccessoryLogging(accessory)
1605
+ accessory.control = new deviceTypes.zb.deviceSensorOccupancy(this, accessory)
1606
+ /** */
1607
+ } else if (platformConsts.devices.zbSensorContact.includes(uiid)) {
1608
+ /**
1609
+ ****************
1610
+ ZB SENSOR [CONTACT]
1611
+ *****************
1612
+ */
1613
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1614
+ this.applyAccessoryLogging(accessory)
1615
+ accessory.control = new deviceTypes.zb.deviceSensorContact(this, accessory)
1616
+ /** */
1617
+ } else if (platformConsts.devices.zbSensorWater.includes(uiid)) {
1618
+ /**
1619
+ *************
1620
+ ZB SENSOR [LEAK]
1621
+ **************
1622
+ */
1623
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1624
+ this.applyAccessoryLogging(accessory)
1625
+ accessory.control = new deviceTypes.zb.deviceSensorLeak(this, accessory)
1626
+ /** */
1627
+ } else if (platformConsts.devices.zbSensorSmoke.includes(uiid)) {
1628
+ /**
1629
+ **************
1630
+ ZB SENSOR [SMOKE]
1631
+ ***************
1632
+ */
1633
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1634
+ this.applyAccessoryLogging(accessory)
1635
+ accessory.control = new deviceTypes.zb.deviceSensorSmoke(this, accessory)
1636
+ /** */
1637
+ } else if (platformConsts.devices.zbThermostat.includes(uiid)) {
1638
+ /**
1639
+ *******************
1640
+ ZB SENSOR [THERMOSTAT]
1641
+ ********************
1642
+ */
1643
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1644
+ this.applyAccessoryLogging(accessory)
1645
+ accessory.control = new deviceTypes.zb.deviceThermostat(this, accessory)
1646
+ /** */
1647
+ } else if (platformConsts.devices.group.includes(uiid)) {
1648
+ /**
1649
+ ***********
1650
+ EWELINK GROUPS
1651
+ ************
1652
+ */
1653
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1654
+ accessory.context.groupUIID = device.uiid
1655
+ this.applyAccessoryLogging(accessory)
1656
+ accessory.control = new deviceTypes.deviceGroup(this, accessory)
1657
+ /** */
1658
+ } else if (platformConsts.devices.camera.includes(uiid)) {
1659
+ /**
1660
+ ***********
1661
+ SONOFF CAMERAS
1662
+ ************
1663
+ */
1664
+ this.log('[%s] %s.', device.name, this.lang.sonoffCamera)
1665
+ this.ignoredDevices.push(device.deviceid)
1666
+ return
1667
+ /** */
1668
+ } else if (platformConsts.devices.template.includes(uiid)) {
1669
+ /**
1670
+ *********
1671
+ TEMPLATE DEV
1672
+ **********
1673
+ */
1674
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1675
+ this.applyAccessoryLogging(accessory)
1676
+ accessory.control = new deviceTypes.deviceTemplate(this, accessory)
1677
+ /** */
1678
+ } else if (platformConsts.devices.cannotSupport.includes(uiid)) {
1679
+ /**
1680
+ *****************************
1681
+ DEVICES THAT CANNOT BE SUPPORTED
1682
+ ******************************
1683
+ */
1684
+ this.log('[%s] %s.', device.name, this.lang.devNotSup)
1685
+ this.ignoredDevices.push(device.deviceid)
1686
+ return
1687
+ /** */
1688
+ } else {
1689
+ /**
1690
+ ********************
1691
+ DEVICES PENDING SUPPORT
1692
+ *********************
1693
+ */
1694
+ this.log.warn(
1695
+ '[%s] %s:\n[%s] %s.',
1696
+ device.name,
1697
+ this.lang.devNotSupYet,
1698
+ uiid,
1699
+ JSON.stringify(device.params),
1700
+ )
1701
+ return
1702
+ /** */
1703
+ }
1704
+
1705
+ // Update the reachability values (via WS and LAN)
1706
+ accessory.context.firmware = device.params.fwVersion || plugin.version
1707
+ accessory.context.reachableWAN = this.wsClient && device.online
1708
+ accessory.context.reachableLAN = this.lanClient && this.lanDevices.has(device.deviceid) && this.lanDevices.get(device.deviceid).ip
1709
+ accessory.context.eweBrandName = device.brandName
1710
+ accessory.context.eweBrandLogo = device.brandLogo
1711
+ accessory.context.eweShared = device?.sharedBy?.email || false
1712
+ accessory.context.ip = this.lanClient && accessory.context.reachableLAN ? this.lanDevices.get(device.deviceid).ip : false
1713
+ accessory.context.macAddress = device.extra?.staMac?.replace(/:+/g, '').replace(/..\B/g, '$&:') || false
1714
+ accessory.context.lanKey = device.devicekey
1715
+
1716
+ // Add the uuid and lanKey to the this.lanClient map
1717
+ if (this.lanClient) {
1718
+ this.lanClient.addDeviceDetailsToMap(device.deviceid, accessory.context)
1719
+ }
1720
+
1721
+ // Helpful logging for each device
1722
+ const str = accessory.context.reachableLAN
1723
+ ? `${this.lang.foundWithIP} [${this.lanDevices.get(device.deviceid).ip}]`
1724
+ : this.lang.lanUnsupported
1725
+
1726
+ // Check to see if the discovered IP is different from any manually configured IP
1727
+ if (
1728
+ accessory.context.reachableLAN
1729
+ && this.ipOverride[device.deviceid]
1730
+ && this.ipOverride[device.deviceid] !== this.lanDevices.get(device.deviceid).ip
1731
+ ) {
1732
+ accessory.logWarn(`${this.lang.lanIPDifferent} [${this.lanDevices.get(device.deviceid).ip}]`)
1733
+ }
1734
+
1735
+ // Check to see if it's a shared device being used with the cloud
1736
+ if (!accessory.context.reachableLAN && accessory.context.eweShared) {
1737
+ accessory.logWarn(this.lang.shareWarn)
1738
+ }
1739
+
1740
+ // Check to see if the device has initially been reported offline
1741
+ if (this.wsClient && !accessory.context.reachableWAN && !accessory.context.reachableLAN) {
1742
+ accessory.logWarn(`${this.lang.repOffline} ${this.lang.viaWS}`)
1743
+ }
1744
+
1745
+ // Update accessory characteristics with the latest values
1746
+ if (this.wsClient && accessory?.control?.externalUpdate) {
1747
+ accessory.control.externalUpdate(device.params)
1748
+
1749
+ // Mark the online/offline status of certain devices
1750
+ if (accessory.control.markStatus) {
1751
+ accessory.control.markStatus(device.online)
1752
+ }
1753
+ }
1754
+
1755
+ // Update any changes to the device into our devicesInHB map
1756
+ this.api.updatePlatformAccessories([accessory])
1757
+ devicesInHB.set(accessory.UUID, accessory)
1758
+ if (uiid === 5000) {
1759
+ this.log(
1760
+ '[%s] %s [%s] [%s].',
1761
+ accessory.displayName,
1762
+ this.lang.devInitGroup,
1763
+ device.deviceid,
1764
+ uiid,
1765
+ )
1766
+ } else {
1767
+ this.log(
1768
+ '[%s] %s %s [%s:%s].',
1769
+ accessory.displayName,
1770
+ this.lang.devInit,
1771
+ str,
1772
+ uiid,
1773
+ device.productModel,
1774
+ )
1775
+ }
1776
+
1777
+ // Store the device in the persist file
1778
+ if (this.storageClientLAN && platformConsts.devices.lan.includes(uiid)) {
1779
+ try {
1780
+ await this.storageLAN.setItem(device.deviceid, device)
1781
+ } catch (err) {
1782
+ accessory.logDebugWarn(`${this.lang.storageWriteErr} ${parseError(err)}`)
1783
+ }
1784
+ }
1785
+ } catch (err) {
1786
+ // Catch any errors during initialisation
1787
+ this.log.warn('[%s] %s %s.', device.name, this.lang.devNotInit, parseError(err))
1788
+ }
1789
+ }
1790
+
1791
+ addAccessory(device, hbDeviceId, hidden = false, extraContext = {}, type = '') {
1792
+ // Add an accessory to Homebridge
1793
+ let newDeviceName = 'Unknown'
1794
+ try {
1795
+ // Get the switchNumber which can be {X, 0, 1, 2, 3, 4, ...}
1796
+ const switchNumber = hbDeviceId.split('SW')[1].toString()
1797
+ const channelCount = type === 'rf_pri'
1798
+ ? Object.keys((device?.tags?.zyx_info) || []).length
1799
+ : platformConsts.supportedDevices[device.extra.uiid]
1800
+
1801
+ // Set up the device name which can depend on the accessory type
1802
+ if (type === 'rf_sub') {
1803
+ // RF accessories have their name stored in the context
1804
+ newDeviceName = extraContext.name
1805
+ } else {
1806
+ // Other accessories store the name initially as the device name
1807
+ newDeviceName = device.name
1808
+
1809
+ // Check if it's a channel of a multi-channel device
1810
+ if (['1', '2', '3', '4'].includes(switchNumber)) {
1811
+ // Try and obtain the eWeLink channel name
1812
+ if (device?.tags?.ck_channel_name?.[Number.parseInt(switchNumber, 10) - 1]) {
1813
+ // Found the eWeLink channel name
1814
+ newDeviceName = device.tags.ck_channel_name[Number.parseInt(switchNumber, 19) - 1]
1815
+ } else {
1816
+ // Didn't find the eWeLink channel name use generic SW channel
1817
+ newDeviceName += ` SW${switchNumber}`
1818
+ }
1819
+ }
1820
+ }
1821
+
1822
+ // Add the new accessory to Homebridge
1823
+ const accessory = new this.api.platformAccessory(
1824
+ newDeviceName,
1825
+ this.api.hap.uuid.generate(hbDeviceId),
1826
+ )
1827
+
1828
+ // If it isn't a hidden device then set the accessory characteristics
1829
+ if (!hidden) {
1830
+ accessory
1831
+ .getService(this.api.hap.Service.AccessoryInformation)
1832
+ .setCharacteristic(this.api.hap.Characteristic.Name, newDeviceName)
1833
+ .setCharacteristic(this.api.hap.Characteristic.ConfiguredName, newDeviceName)
1834
+ .setCharacteristic(this.api.hap.Characteristic.SerialNumber, hbDeviceId)
1835
+ .setCharacteristic(this.api.hap.Characteristic.Manufacturer, device.brandName)
1836
+ .setCharacteristic(
1837
+ this.api.hap.Characteristic.Model,
1838
+ `${device.productModel} (${device.extra.model})`,
1839
+ )
1840
+ .setCharacteristic(
1841
+ this.api.hap.Characteristic.FirmwareRevision,
1842
+ device.params.fwVersion || plugin.version,
1843
+ )
1844
+ .setCharacteristic(this.api.hap.Characteristic.Identify, true)
1845
+ }
1846
+
1847
+ // Add helpful context values to the accessory
1848
+ accessory.context = {
1849
+ ...{
1850
+ hbDeviceId,
1851
+ eweDeviceId: device.deviceid,
1852
+ eweUIID: device.extra.uiid,
1853
+ eweModel: device.productModel,
1854
+ eweApiKey: device.apikey,
1855
+ switchNumber,
1856
+ channelCount,
1857
+ hidden,
1858
+ },
1859
+ ...extraContext,
1860
+ }
1861
+
1862
+ // Register the accessory if it hasn't been hidden by the user
1863
+ if (!hidden) {
1864
+ this.api.registerPlatformAccessories(plugin.name, plugin.alias, [accessory])
1865
+ this.log('[%s] %s.', newDeviceName, this.lang.devAdd)
1866
+ }
1867
+
1868
+ // Return the new accessory
1869
+ this.configureAccessory(accessory)
1870
+ return accessory
1871
+ } catch (err) {
1872
+ // Catch any errors during add
1873
+ this.log.warn('[%s] %s %s.', newDeviceName, this.lang.devNotAdd, parseError(err))
1874
+ return false
1875
+ }
1876
+ }
1877
+
1878
+ addExternalAccessory(device, hbDeviceId, category) {
1879
+ try {
1880
+ // Add the new accessory to Homebridge
1881
+ const accessory = new this.api.platformAccessory(
1882
+ device.name,
1883
+ this.api.hap.uuid.generate(hbDeviceId),
1884
+ category,
1885
+ )
1886
+
1887
+ // Set the accessory characteristics
1888
+ accessory
1889
+ .getService(this.api.hap.Service.AccessoryInformation)
1890
+ .setCharacteristic(this.api.hap.Characteristic.Name, device.name)
1891
+ .setCharacteristic(this.api.hap.Characteristic.ConfiguredName, device.name)
1892
+ .setCharacteristic(this.api.hap.Characteristic.SerialNumber, hbDeviceId)
1893
+ .setCharacteristic(this.api.hap.Characteristic.Manufacturer, device.brandName)
1894
+ .setCharacteristic(
1895
+ this.api.hap.Characteristic.Model,
1896
+ `${device.productModel} (${device.extra.model})`,
1897
+ )
1898
+ .setCharacteristic(
1899
+ this.api.hap.Characteristic.FirmwareRevision,
1900
+ device.params.fwVersion || plugin.version,
1901
+ )
1902
+ .setCharacteristic(this.api.hap.Characteristic.Identify, true)
1903
+
1904
+ // Add helpful context values to the accessory
1905
+ accessory.context = {
1906
+ ...{
1907
+ hbDeviceId,
1908
+ eweDeviceId: device.deviceid,
1909
+ eweUIID: device.extra.uiid,
1910
+ eweModel: device.productModel,
1911
+ eweApiKey: device.apikey,
1912
+ switchNumber: hbDeviceId.split('SW')[1].toString(),
1913
+ channelCount: platformConsts.supportedDevices[device.extra.uiid],
1914
+ hidden: false,
1915
+ },
1916
+ }
1917
+
1918
+ // Register the accessory
1919
+ this.api.publishExternalAccessories(plugin.name, [accessory])
1920
+ this.log('[%s] %s.', device.name, this.lang.devAdd)
1921
+
1922
+ // Return the new accessory
1923
+ this.configureAccessory(accessory)
1924
+ return accessory
1925
+ } catch (err) {
1926
+ // Catch any errors during add
1927
+ this.log.warn('[%s] %s %s.', device.name, this.lang.devNotAdd, parseError(err))
1928
+ return false
1929
+ }
1930
+ }
1931
+
1932
+ configureAccessory(accessory) {
1933
+ // Set the correct firmware version if we can
1934
+ if (this.api && accessory.context.firmware) {
1935
+ accessory
1936
+ .getService(this.api.hap.Service.AccessoryInformation)
1937
+ .updateCharacteristic(
1938
+ this.api.hap.Characteristic.FirmwareRevision,
1939
+ accessory.context.firmware,
1940
+ )
1941
+ }
1942
+
1943
+ // Add the configured accessory to our global map
1944
+ devicesInHB.set(accessory.UUID, accessory)
1945
+ }
1946
+
1947
+ removeAccessory(accessory) {
1948
+ try {
1949
+ // Remove an accessory from Homebridge
1950
+ if (!accessory.context.hidden) {
1951
+ this.api.unregisterPlatformAccessories(plugin.name, plugin.alias, [accessory])
1952
+ }
1953
+ devicesInHB.delete(accessory.UUID)
1954
+ this.log('[%s] %s.', accessory.displayName, this.lang.devRemove)
1955
+ } catch (err) {
1956
+ // Catch any errors during remove
1957
+ this.log.warn('[%s] %s %s.', accessory.displayName, this.lang.devNotRemove, parseError(err))
1958
+ }
1959
+ }
1960
+
1961
+ async sendDeviceUpdate(accessory, params) {
1962
+ // Add to a queue so multiple updates are at least 500ms apart
1963
+ return queue.add(async () => {
1964
+ // Log the update being sent
1965
+ accessory.logDebug(`${this.lang.updSend} ${JSON.stringify(params)}`)
1966
+
1967
+ // Set up the payload to send via LAN/WS
1968
+ const payload = {
1969
+ apikey: accessory.context.eweApiKey,
1970
+ deviceid: accessory.context.eweDeviceId,
1971
+ params,
1972
+ }
1973
+
1974
+ // Check if we can send via LAN otherwise send via WS
1975
+ let res
1976
+ if (this.lanClient) {
1977
+ res = platformConsts.devices.lan.includes(accessory.context.eweUIID)
1978
+ ? await this.lanClient.sendUpdate(payload)
1979
+ : this.lang.lanNotSup
1980
+ } else {
1981
+ res = this.lang.lanDisabled
1982
+ }
1983
+
1984
+ // Revert to WS if LAN mode not possible for whatever reason
1985
+ if (res !== 'ok') {
1986
+ // Check to see if the device is online
1987
+ if (this.wsClient) {
1988
+ // Log the revert if appropriate
1989
+ accessory.logDebug(`${this.lang.revertWS} ${res}`)
1990
+
1991
+ // Attempt the update
1992
+ if (accessory.context.reachableWAN) {
1993
+ // Device is online via WS so send the update
1994
+ return this.wsClient.sendUpdate(payload)
1995
+ }
1996
+ // Device appears to be offline
1997
+ throw new Error(this.lang.unreachable)
1998
+ } else {
1999
+ // Device isn't online via WS so report the error back
2000
+ const eText = [this.lang.lanDisabled, this.lang.lanNotSup].includes(res)
2001
+ ? res
2002
+ : `${this.lang.unreachable} [${res}]`
2003
+ throw new Error(eText)
2004
+ }
2005
+ }
2006
+ return true
2007
+ })
2008
+ }
2009
+
2010
+ async sendGroupUpdate(accessory, params) {
2011
+ // Add to a queue so multiple updates are at least 500ms apart
2012
+ return queue.add(async () => {
2013
+ // Log the update being sent
2014
+ accessory.logDebug(`${this.lang.updSend} ${JSON.stringify(params)}`)
2015
+
2016
+ // Send the request via HTTP
2017
+ this.httpClient.updateGroup(accessory.context.eweDeviceId, params)
2018
+ })
2019
+ }
2020
+
2021
+ async receiveDeviceUpdate(device) {
2022
+ const deviceId = device.deviceid
2023
+ let reachableChange = false
2024
+
2025
+ // Find our accessory for which the updates relate to
2026
+ const uuid1 = this.api.hap.uuid.generate(`${deviceId}SWX`)
2027
+ const uuid2 = this.api.hap.uuid.generate(`${deviceId}SW0`)
2028
+ const accessory = devicesInHB.get(uuid1) || devicesInHB.get(uuid2)
2029
+ if (!accessory) {
2030
+ return
2031
+ }
2032
+ accessory.logDebug(`${this.lang.updRec} ${JSON.stringify(device.params)}`)
2033
+ if (device.params.updateSource === 'WS') {
2034
+ // The update is from WS so update the WS online/offline status
2035
+ if (device.params.online !== accessory.context.reachableWAN) {
2036
+ accessory.context.reachableWAN = device.params.online
2037
+ this.api.updatePlatformAccessories([accessory])
2038
+ devicesInHB.set(accessory.UUID, accessory)
2039
+
2040
+ // Flag this true to update the sub accessories later
2041
+ reachableChange = true
2042
+
2043
+ // Log the new reachability of the device
2044
+ if (accessory.context.reachableWAN) {
2045
+ accessory.logDebug(`${this.lang.repOnline} ${this.lang.viaWS}`)
2046
+ } else {
2047
+ accessory.logDebugWarn(`${this.lang.repOffline} ${this.lang.viaWS}`)
2048
+ }
2049
+
2050
+ // Mark the online/offline status of certain devices
2051
+ if (accessory.control.markStatus) {
2052
+ accessory.control.markStatus(device.params.online)
2053
+ }
2054
+
2055
+ // Try and request an update through WS if the device has come back online
2056
+ if (accessory.context.reachableWAN && this.wsClient) {
2057
+ try {
2058
+ await this.wsClient.requestUpdate(accessory)
2059
+ } catch (err) {
2060
+ // Suppress any errors here
2061
+ }
2062
+ }
2063
+ }
2064
+ }
2065
+ if (device.params.updateSource === 'LAN') {
2066
+ // The update is from LAN so it must be online
2067
+ if (!accessory.context.reachableLAN) {
2068
+ accessory.context.reachableLAN = true
2069
+
2070
+ // Flag this true to update the sub accessories later
2071
+ reachableChange = true
2072
+
2073
+ // Log the new reachability of the device
2074
+ accessory.log(`${this.lang.repOnline} ${this.lang.viaLAN}`)
2075
+
2076
+ // Try and request an update through WS if the device has come back online
2077
+ if (accessory.context.reachableWAN && this.wsClient) {
2078
+ try {
2079
+ await this.wsClient.requestUpdate(accessory)
2080
+ } catch (err) {
2081
+ // Suppress any errors here
2082
+ }
2083
+ }
2084
+ }
2085
+
2086
+ // Check to see if the IP of the device has changed
2087
+ if (device.params.ip && device.params.ip !== accessory.context.ip) {
2088
+ accessory.context.ip = device.params.ip
2089
+
2090
+ // Flag this true to update the sub accessories later
2091
+ reachableChange = true
2092
+
2093
+ // Log the new ip of the device
2094
+ accessory.log(`${this.lang.newIP} [${device.params.ip}]`)
2095
+ }
2096
+
2097
+ // Update the accessory context if the device is back online or the IP changed
2098
+ if (reachableChange) {
2099
+ this.api.updatePlatformAccessories([accessory])
2100
+ devicesInHB.set(accessory.UUID, accessory)
2101
+ }
2102
+ }
2103
+
2104
+ // Update this new online/offline status for all switches of multichannel devices
2105
+ if (reachableChange && accessory.context.hbDeviceId.substr(-1) !== 'X') {
2106
+ // Loop through to see which channels are in HB
2107
+ for (let i = 1; i <= accessory.context.channelCount; i += 1) {
2108
+ const uuid = this.api.hap.uuid.generate(`${deviceId}SW${i}`)
2109
+ if (devicesInHB.has(uuid)) {
2110
+ // Find the sub accessory
2111
+ const subAccessory = devicesInHB.get(uuid)
2112
+
2113
+ // Update the WAN status
2114
+ subAccessory.context.reachableWAN = device.params.online
2115
+ if (device.params.updateSource === 'WS' && subAccessory?.control?.markStatus) {
2116
+ subAccessory.control.markStatus(device.params.online)
2117
+ }
2118
+
2119
+ // Update the LAN status
2120
+ if (device.params.updateSource === 'LAN') {
2121
+ subAccessory.context.reachableLAN = true
2122
+ if (device.params.ip) {
2123
+ subAccessory.context.ip = device.params.ip
2124
+ }
2125
+ }
2126
+
2127
+ // Save the sub accessory updates to the platform
2128
+ this.api.updatePlatformAccessories([subAccessory])
2129
+ devicesInHB.set(subAccessory.UUID, subAccessory)
2130
+ }
2131
+ }
2132
+ }
2133
+ try {
2134
+ // Update the accessory with the new data
2135
+ if (accessory?.control?.externalUpdate) {
2136
+ accessory.control.externalUpdate(device.params)
2137
+ }
2138
+ } catch (err) {
2139
+ this.log.warn('[%s] %s %s.', accessory.displayName, this.lang.devNotRf, parseError(err))
2140
+ }
2141
+ }
2142
+
2143
+ async deviceUpdateError(accessory, err, requestRefresh) {
2144
+ // Log the error
2145
+ this.log.warn('[%s] %s %s.', accessory.displayName, this.lang.devNotUpd, parseError(err))
2146
+
2147
+ // We only request a device refresh on failed internal updates
2148
+ if (requestRefresh && accessory.context.reachableWAN && this.wsClient) {
2149
+ try {
2150
+ await this.wsClient.requestUpdate(accessory)
2151
+ } catch (error) {
2152
+ // Suppress any errors at this point
2153
+ }
2154
+ }
2155
+ }
2156
+ }