@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.
- package/CHANGELOG.md +1363 -0
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/config.schema.json +1757 -0
- package/eslint.config.js +15 -0
- package/lib/connection/api.js +508 -0
- package/lib/connection/http.js +304 -0
- package/lib/connection/lan.js +318 -0
- package/lib/connection/ws.js +414 -0
- package/lib/device/curtain.js +324 -0
- package/lib/device/diffuser.js +473 -0
- package/lib/device/fan.js +349 -0
- package/lib/device/group.js +106 -0
- package/lib/device/humidifier.js +199 -0
- package/lib/device/index.js +61 -0
- package/lib/device/light-cct.js +333 -0
- package/lib/device/light-dimmer.js +257 -0
- package/lib/device/light-rgb-cct.js +809 -0
- package/lib/device/light-rgb.js +272 -0
- package/lib/device/motor.js +363 -0
- package/lib/device/outlet-multi.js +417 -0
- package/lib/device/outlet-single-inched.js +409 -0
- package/lib/device/outlet-single.js +384 -0
- package/lib/device/panel.js +382 -0
- package/lib/device/rf-bridge.js +321 -0
- package/lib/device/rf-button.js +125 -0
- package/lib/device/rf-sensor.js +173 -0
- package/lib/device/sensor-ambient.js +311 -0
- package/lib/device/sensor-contact.js +140 -0
- package/lib/device/sensor-temp-humi.js +182 -0
- package/lib/device/simulation/blind.js +253 -0
- package/lib/device/simulation/cooler.js +312 -0
- package/lib/device/simulation/door.js +253 -0
- package/lib/device/simulation/doorbell.js +190 -0
- package/lib/device/simulation/garage-eachen.js +235 -0
- package/lib/device/simulation/garage-four.js +176 -0
- package/lib/device/simulation/garage-od-switch.js +36 -0
- package/lib/device/simulation/garage-one.js +395 -0
- package/lib/device/simulation/garage-two.js +394 -0
- package/lib/device/simulation/gate-one.js +327 -0
- package/lib/device/simulation/heater.js +389 -0
- package/lib/device/simulation/index.js +75 -0
- package/lib/device/simulation/light-fan.js +258 -0
- package/lib/device/simulation/lock-eachen.js +163 -0
- package/lib/device/simulation/lock-one.js +366 -0
- package/lib/device/simulation/p-button.js +223 -0
- package/lib/device/simulation/purifier.js +289 -0
- package/lib/device/simulation/rf-blind.js +192 -0
- package/lib/device/simulation/rf-door.js +180 -0
- package/lib/device/simulation/rf-window.js +180 -0
- package/lib/device/simulation/sensor-hidden.js +105 -0
- package/lib/device/simulation/sensor-leak.js +123 -0
- package/lib/device/simulation/sensor-visible.js +231 -0
- package/lib/device/simulation/sensor.js +300 -0
- package/lib/device/simulation/switch-valve.js +351 -0
- package/lib/device/simulation/tap-one.js +283 -0
- package/lib/device/simulation/tap-two.js +266 -0
- package/lib/device/simulation/th-cooler.js +443 -0
- package/lib/device/simulation/th-dehumidifier.js +424 -0
- package/lib/device/simulation/th-heater.js +449 -0
- package/lib/device/simulation/th-humidifier.js +424 -0
- package/lib/device/simulation/th-thermostat.js +611 -0
- package/lib/device/simulation/tv.js +252 -0
- package/lib/device/simulation/valve-four.js +232 -0
- package/lib/device/simulation/valve-one.js +345 -0
- package/lib/device/simulation/valve-two.js +340 -0
- package/lib/device/simulation/window.js +253 -0
- package/lib/device/switch-man.js +106 -0
- package/lib/device/switch-mate.js +82 -0
- package/lib/device/switch-multi.js +411 -0
- package/lib/device/switch-single-inched.js +332 -0
- package/lib/device/switch-single.js +307 -0
- package/lib/device/template.js +43 -0
- package/lib/device/thermostat.js +286 -0
- package/lib/device/zigbee/index.js +27 -0
- package/lib/device/zigbee/light-cct.js +263 -0
- package/lib/device/zigbee/light-dimmer.js +148 -0
- package/lib/device/zigbee/light-rgb-cct.js +400 -0
- package/lib/device/zigbee/motor.js +301 -0
- package/lib/device/zigbee/sensor-ambient.js +177 -0
- package/lib/device/zigbee/sensor-contact.js +137 -0
- package/lib/device/zigbee/sensor-leak.js +119 -0
- package/lib/device/zigbee/sensor-motion.js +119 -0
- package/lib/device/zigbee/sensor-occupancy.js +119 -0
- package/lib/device/zigbee/sensor-smoke.js +96 -0
- package/lib/device/zigbee/switch-stateless.js +104 -0
- package/lib/device/zigbee/thermostat.js +323 -0
- package/lib/fakegato/LICENSE +21 -0
- package/lib/fakegato/fakegato-history.js +813 -0
- package/lib/fakegato/fakegato-storage.js +108 -0
- package/lib/fakegato/fakegato-timer.js +125 -0
- package/lib/fakegato/uuid.js +27 -0
- package/lib/homebridge-ui/public/index.html +365 -0
- package/lib/homebridge-ui/server.js +46 -0
- package/lib/index.js +7 -0
- package/lib/node-dns-sd/LICENSE +21 -0
- package/lib/node-dns-sd/README.md +613 -0
- package/lib/node-dns-sd/lib/dns-sd-classes.json +6 -0
- package/lib/node-dns-sd/lib/dns-sd-composer.js +83 -0
- package/lib/node-dns-sd/lib/dns-sd-parser.js +344 -0
- package/lib/node-dns-sd/lib/dns-sd-types.json +52 -0
- package/lib/node-dns-sd/lib/dns-sd.js +580 -0
- package/lib/platform.js +2156 -0
- package/lib/utils/colour.js +552 -0
- package/lib/utils/constants.js +589 -0
- package/lib/utils/custom-chars.js +33 -0
- package/lib/utils/eve-chars.js +130 -0
- package/lib/utils/functions.js +42 -0
- package/lib/utils/lang-en.js +160 -0
- package/lib/utils/lang-fr.js +163 -0
- package/lib/utils/lang-th.js +160 -0
- package/lib/utils/lang.js +5 -0
- package/package.json +75 -0
package/lib/platform.js
ADDED
|
@@ -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
|
+
}
|