@homebridge-plugins/homebridge-govee 11.32.2-beta.2 → 11.32.2-beta.21
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 +31 -0
- package/config.schema.json +1 -1
- package/lib/connection/ble.js +26 -1
- package/lib/connection/openapi.js +18 -0
- package/lib/device/appliance-basic.js +377 -0
- package/lib/device/dehumidifier-H7150.js +9 -3
- package/lib/device/diffuser-H7161.js +7 -1
- package/lib/device/fan-H1370.js +7 -1
- package/lib/device/fan-H7100.js +36 -4
- package/lib/device/fan-H7105.js +7 -1
- package/lib/device/fan-H7107.js +7 -1
- package/lib/device/heater1a.js +52 -12
- package/lib/device/heater1b.js +14 -3
- package/lib/device/heater2.js +7 -1
- package/lib/device/humidifier-H7140.js +7 -1
- package/lib/device/humidifier-H7141.js +7 -1
- package/lib/device/humidifier-H7142.js +7 -1
- package/lib/device/humidifier-H7143.js +7 -1
- package/lib/device/humidifier-H7160.js +7 -1
- package/lib/device/ice-maker-H7172.js +19 -7
- package/lib/device/index.js +44 -51
- package/lib/device/kettle.js +7 -1
- package/lib/device/outlet-single.js +7 -1
- package/lib/device/purifier-H7120.js +14 -25
- package/lib/device/purifier-H7122.js +7 -1
- package/lib/device/purifier-H7123.js +9 -3
- package/lib/device/purifier-H7126.js +7 -1
- package/lib/device/{sensor-button.js → sensor-basic.js} +14 -2
- package/lib/device/sensor-presence.js +7 -1
- package/lib/device/sensor-thermo4.js +13 -2
- package/lib/device/switch-double.js +34 -15
- package/lib/device/switch-triple.js +36 -17
- package/lib/device/template.js +15 -2
- package/lib/platform.js +83 -799
- package/lib/utils/capabilities.js +220 -0
- package/lib/utils/command-builder.js +6 -4
- package/lib/utils/config-parser.js +280 -0
- package/lib/utils/constants.js +120 -8
- package/lib/utils/decode.js +4 -1
- package/lib/utils/device-capabilities.js +77 -3
- package/lib/utils/device-context.js +179 -0
- package/lib/utils/device-merge.js +251 -0
- package/lib/utils/dispatch.js +159 -0
- package/lib/utils/functions.js +9 -0
- package/lib/utils/lang-en.js +0 -2
- package/lib/utils/report-unknown.js +156 -0
- package/package.json +1 -1
- package/lib/device/dehumidifier-H7151.js +0 -189
- package/lib/device/dehumidifier-H7152.js +0 -180
- package/lib/device/diffuser-H7162.js +0 -123
- package/lib/device/fan-H1310.js +0 -284
- package/lib/device/fan-H7101.js +0 -340
- package/lib/device/fan-H7102.js +0 -284
- package/lib/device/fan-H7106.js +0 -284
- package/lib/device/fan-H7111.js +0 -345
- package/lib/device/humidifier-H7145.js +0 -187
- package/lib/device/humidifier-H7147.js +0 -187
- package/lib/device/humidifier-H7148.js +0 -187
- package/lib/device/humidifier-H7149.js +0 -187
- package/lib/device/humidifier-H714E.js +0 -187
- package/lib/device/ice-maker-H717D.js +0 -218
- package/lib/device/ice-maker-H8120.js +0 -218
- package/lib/device/outlet-double.js +0 -188
- package/lib/device/outlet-triple.js +0 -160
- package/lib/device/purifier-H7121.js +0 -406
- package/lib/device/purifier-H7124.js +0 -504
- package/lib/device/purifier-H7127.js +0 -359
- package/lib/device/purifier-H7128.js +0 -359
- package/lib/device/purifier-H7129.js +0 -359
- package/lib/device/purifier-H712C.js +0 -359
- package/lib/device/sensor-contact.js +0 -22
- package/lib/utils/ble-timeouts.test.js +0 -44
- package/lib/utils/functions.test.js +0 -292
package/lib/platform.js
CHANGED
|
@@ -11,23 +11,25 @@ import awsClient from './connection/aws.js'
|
|
|
11
11
|
import httpClient from './connection/http.js'
|
|
12
12
|
import lanClient from './connection/lan.js'
|
|
13
13
|
import openApiClient from './connection/openapi.js'
|
|
14
|
-
import deviceTypes from './device/index.js'
|
|
15
14
|
import eveService from './fakegato/fakegato-history.js'
|
|
16
15
|
import { BLE_UPDATE_TIMEOUT } from './utils/ble-protocol.js'
|
|
17
16
|
import { buildCommand } from './utils/command-builder.js'
|
|
17
|
+
import { applyUserConfig } from './utils/config-parser.js'
|
|
18
18
|
import platformConsts from './utils/constants.js'
|
|
19
19
|
import platformChars from './utils/custom-chars.js'
|
|
20
|
-
import {
|
|
20
|
+
import { applyDeviceContext } from './utils/device-context.js'
|
|
21
|
+
import { cloudListsWereFetched, planDeviceSetup, planRedundantAccessories } from './utils/device-merge.js'
|
|
22
|
+
import { resolveDeviceType } from './utils/dispatch.js'
|
|
21
23
|
import eveChars from './utils/eve-chars.js'
|
|
22
24
|
import {
|
|
23
25
|
base64ToHex,
|
|
24
26
|
hasProperty,
|
|
25
|
-
parseDeviceId,
|
|
26
27
|
parseError,
|
|
27
28
|
pfxToCertAndKey,
|
|
28
29
|
updateFailureError,
|
|
29
30
|
} from './utils/functions.js'
|
|
30
31
|
import platformLang from './utils/lang-en.js'
|
|
32
|
+
import { logUnknownData } from './utils/report-unknown.js'
|
|
31
33
|
import { parseDeviceUpdate } from './utils/response-parser.js'
|
|
32
34
|
|
|
33
35
|
const require = createRequire(import.meta.url)
|
|
@@ -85,7 +87,7 @@ export default class {
|
|
|
85
87
|
|
|
86
88
|
// Apply the user's configuration
|
|
87
89
|
this.config = platformConsts.defaultConfig
|
|
88
|
-
|
|
90
|
+
applyUserConfig(this, config)
|
|
89
91
|
|
|
90
92
|
// Set up empty clients
|
|
91
93
|
this.bleClient = false
|
|
@@ -103,269 +105,6 @@ export default class {
|
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
applyUserConfig(config) {
|
|
107
|
-
// These shorthand functions save line space during config parsing
|
|
108
|
-
const logDefault = (k, def) => {
|
|
109
|
-
this.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgDef, def)
|
|
110
|
-
}
|
|
111
|
-
const logDuplicate = (k) => {
|
|
112
|
-
this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgDup)
|
|
113
|
-
}
|
|
114
|
-
const logIgnore = (k) => {
|
|
115
|
-
this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgIgn)
|
|
116
|
-
}
|
|
117
|
-
const logIgnoreItem = (k) => {
|
|
118
|
-
this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgIgnItem)
|
|
119
|
-
}
|
|
120
|
-
const logIncrease = (k, min) => {
|
|
121
|
-
this.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgLow, min)
|
|
122
|
-
}
|
|
123
|
-
const logQuotes = (k) => {
|
|
124
|
-
this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgQts)
|
|
125
|
-
}
|
|
126
|
-
const logRemove = (k) => {
|
|
127
|
-
this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgRmv)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Begin applying the user's config
|
|
131
|
-
Object.entries(config).forEach((entry) => {
|
|
132
|
-
const [key, val] = entry
|
|
133
|
-
switch (key) {
|
|
134
|
-
case 'apiKey':
|
|
135
|
-
case 'code':
|
|
136
|
-
case 'password':
|
|
137
|
-
case 'username':
|
|
138
|
-
if (typeof val !== 'string' || val === '') {
|
|
139
|
-
logIgnore(key)
|
|
140
|
-
} else {
|
|
141
|
-
this.config[key] = val
|
|
142
|
-
}
|
|
143
|
-
break
|
|
144
|
-
case 'lanScanSubnets': {
|
|
145
|
-
// Comma-separated list of CIDR subnets to scan directly (for devices
|
|
146
|
-
// on a different subnet from Homebridge, which multicast can't reach)
|
|
147
|
-
if (typeof val !== 'string' || val.trim() === '') {
|
|
148
|
-
break
|
|
149
|
-
}
|
|
150
|
-
const subnets = val
|
|
151
|
-
.split(',')
|
|
152
|
-
.map(s => s.trim())
|
|
153
|
-
.filter(s => /^\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}$/.test(s))
|
|
154
|
-
if (subnets.length === 0) {
|
|
155
|
-
logIgnore(key)
|
|
156
|
-
} else {
|
|
157
|
-
this.config[key] = subnets
|
|
158
|
-
}
|
|
159
|
-
break
|
|
160
|
-
}
|
|
161
|
-
case 'awsDisable':
|
|
162
|
-
case 'bleDisable':
|
|
163
|
-
case 'colourSafeMode':
|
|
164
|
-
case 'disableDeviceLogging':
|
|
165
|
-
case 'ignoreMatter':
|
|
166
|
-
case 'lanDisable':
|
|
167
|
-
if (typeof val === 'string') {
|
|
168
|
-
logQuotes(key)
|
|
169
|
-
}
|
|
170
|
-
this.config[key] = val === 'false' ? false : !!val
|
|
171
|
-
break
|
|
172
|
-
case 'bleControlInterval':
|
|
173
|
-
case 'bleRefreshTime':
|
|
174
|
-
case 'httpRefreshTime':
|
|
175
|
-
case 'lanRefreshTime':
|
|
176
|
-
case 'lanScanInterval': {
|
|
177
|
-
if (typeof val === 'string') {
|
|
178
|
-
logQuotes(key)
|
|
179
|
-
}
|
|
180
|
-
const intVal = Number.parseInt(val, 10)
|
|
181
|
-
if (Number.isNaN(intVal)) {
|
|
182
|
-
logDefault(key, platformConsts.defaultValues[key])
|
|
183
|
-
this.config[key] = platformConsts.defaultValues[key]
|
|
184
|
-
} else if (intVal < platformConsts.minValues[key]) {
|
|
185
|
-
logIncrease(key, platformConsts.minValues[key])
|
|
186
|
-
this.config[key] = platformConsts.minValues[key]
|
|
187
|
-
} else {
|
|
188
|
-
this.config[key] = intVal
|
|
189
|
-
}
|
|
190
|
-
break
|
|
191
|
-
}
|
|
192
|
-
case 'dehumidifierDevices':
|
|
193
|
-
case 'fanDevices':
|
|
194
|
-
case 'heaterDevices':
|
|
195
|
-
case 'humidifierDevices':
|
|
196
|
-
case 'iceMakerDevices':
|
|
197
|
-
case 'kettleDevices':
|
|
198
|
-
case 'leakDevices':
|
|
199
|
-
case 'lightDevices':
|
|
200
|
-
case 'purifierDevices':
|
|
201
|
-
case 'diffuserDevices':
|
|
202
|
-
case 'switchDevices':
|
|
203
|
-
case 'thermoDevices':
|
|
204
|
-
if (Array.isArray(val) && val.length > 0) {
|
|
205
|
-
val.forEach((x) => {
|
|
206
|
-
if (!x.deviceId) {
|
|
207
|
-
logIgnoreItem(key)
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
const id = parseDeviceId(x.deviceId)
|
|
211
|
-
if (Object.keys(this.deviceConf).includes(id)) {
|
|
212
|
-
logDuplicate(`${key}.${id}`)
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
const entries = Object.entries(x)
|
|
216
|
-
if (entries.length === 1) {
|
|
217
|
-
logRemove(`${key}.${id}`)
|
|
218
|
-
return
|
|
219
|
-
}
|
|
220
|
-
this.deviceConf[id] = {}
|
|
221
|
-
entries.forEach((subEntry) => {
|
|
222
|
-
const [k, v] = subEntry
|
|
223
|
-
switch (k) {
|
|
224
|
-
case 'adaptiveLightingShift':
|
|
225
|
-
case 'brightnessStep':
|
|
226
|
-
case 'lowBattThreshold': {
|
|
227
|
-
if (typeof v === 'string') {
|
|
228
|
-
logQuotes(`${key}.${k}`)
|
|
229
|
-
}
|
|
230
|
-
const intVal = Number.parseInt(v, 10)
|
|
231
|
-
if (Number.isNaN(intVal)) {
|
|
232
|
-
logDefault(`${key}.${id}.${k}`, platformConsts.defaultValues[k])
|
|
233
|
-
this.deviceConf[id][k] = platformConsts.defaultValues[k]
|
|
234
|
-
} else if (intVal < platformConsts.minValues[k]) {
|
|
235
|
-
logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k])
|
|
236
|
-
this.deviceConf[id][k] = platformConsts.minValues[k]
|
|
237
|
-
} else {
|
|
238
|
-
this.deviceConf[id][k] = intVal
|
|
239
|
-
}
|
|
240
|
-
break
|
|
241
|
-
}
|
|
242
|
-
case 'awsBrightnessNoScale':
|
|
243
|
-
case 'hideLight':
|
|
244
|
-
case 'hideModeGreenTea':
|
|
245
|
-
case 'hideModeOolongTea':
|
|
246
|
-
case 'hideModeCoffee':
|
|
247
|
-
case 'hideModeBlackTea':
|
|
248
|
-
case 'showCustomMode1':
|
|
249
|
-
case 'showCustomMode2':
|
|
250
|
-
case 'showExtraSwitch':
|
|
251
|
-
case 'tempReporting':
|
|
252
|
-
if (typeof v === 'string') {
|
|
253
|
-
logQuotes(`${key}.${id}.${k}`)
|
|
254
|
-
}
|
|
255
|
-
this.deviceConf[id][k] = v === 'false' ? false : !!v
|
|
256
|
-
break
|
|
257
|
-
case 'awsColourMode':
|
|
258
|
-
case 'openApiTempUnit':
|
|
259
|
-
case 'showAs': {
|
|
260
|
-
if (typeof v !== 'string' || !platformConsts.allowed[k].includes(v)) {
|
|
261
|
-
logIgnore(`${key}.${id}.${k}`)
|
|
262
|
-
} else {
|
|
263
|
-
this.deviceConf[id][k] = v
|
|
264
|
-
}
|
|
265
|
-
break
|
|
266
|
-
}
|
|
267
|
-
case 'customAddress':
|
|
268
|
-
case 'customIPAddress':
|
|
269
|
-
if (typeof v !== 'string' || v === '') {
|
|
270
|
-
logIgnore(`${key}.${id}.${k}`)
|
|
271
|
-
} else {
|
|
272
|
-
this.deviceConf[id][k] = v.replace(WHITESPACE_REGEX, '')
|
|
273
|
-
}
|
|
274
|
-
break
|
|
275
|
-
case 'deviceId':
|
|
276
|
-
break
|
|
277
|
-
case 'diyMode':
|
|
278
|
-
case 'diyModeTwo':
|
|
279
|
-
case 'diyModeThree':
|
|
280
|
-
case 'diyModeFour':
|
|
281
|
-
case 'musicMode':
|
|
282
|
-
case 'musicModeTwo':
|
|
283
|
-
case 'scene':
|
|
284
|
-
case 'sceneTwo':
|
|
285
|
-
case 'sceneThree':
|
|
286
|
-
case 'sceneFour':
|
|
287
|
-
case 'segmented':
|
|
288
|
-
case 'segmentedTwo':
|
|
289
|
-
case 'segmentedThree':
|
|
290
|
-
case 'segmentedFour':
|
|
291
|
-
case 'temperatureSource':
|
|
292
|
-
case 'videoMode':
|
|
293
|
-
case 'videoModeTwo': {
|
|
294
|
-
if (typeof v === 'string') {
|
|
295
|
-
this.log.warn(`${key}.${id}.${k} incorrectly configured - please use the config screen to reconfigure this item:`)
|
|
296
|
-
this.log.warn(`${key}.${id}.${k}: ${v}`)
|
|
297
|
-
}
|
|
298
|
-
if (typeof v === 'object') {
|
|
299
|
-
// object - only allowed keys are 'sceneCode', 'bleCode' and 'showAs'
|
|
300
|
-
const subEntries = Object.entries(v)
|
|
301
|
-
if (subEntries.length > 0) {
|
|
302
|
-
this.deviceConf[id][k] = {}
|
|
303
|
-
subEntries.forEach((subSubEntry) => {
|
|
304
|
-
const [k1, v1] = subSubEntry
|
|
305
|
-
switch (k1) {
|
|
306
|
-
case 'bleCode':
|
|
307
|
-
case 'sceneCode':
|
|
308
|
-
if (typeof v1 !== 'string' || v1 === '') {
|
|
309
|
-
logIgnore(`${key}.${id}.${k}.${k1}`)
|
|
310
|
-
} else {
|
|
311
|
-
this.deviceConf[id][k][k1] = v1
|
|
312
|
-
}
|
|
313
|
-
break
|
|
314
|
-
case 'showAs': {
|
|
315
|
-
if (typeof v1 !== 'string' || !['default', 'switch'].includes(v1)) {
|
|
316
|
-
logIgnore(`${key}.${id}.${k}.${k1}`)
|
|
317
|
-
} else {
|
|
318
|
-
this.deviceConf[id][k][k1] = v1
|
|
319
|
-
}
|
|
320
|
-
break
|
|
321
|
-
}
|
|
322
|
-
default:
|
|
323
|
-
logIgnore(`${key}.${id}.${k}.${k1}`)
|
|
324
|
-
break
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
} else {
|
|
328
|
-
logIgnore(`${key}.${id}.${k}`)
|
|
329
|
-
}
|
|
330
|
-
} else {
|
|
331
|
-
logIgnore(`${key}.${id}.${k}`)
|
|
332
|
-
}
|
|
333
|
-
break
|
|
334
|
-
}
|
|
335
|
-
case 'ignoreDevice':
|
|
336
|
-
if (typeof v === 'string') {
|
|
337
|
-
logQuotes(`${key}.${id}.${k}`)
|
|
338
|
-
}
|
|
339
|
-
if (!!v && v !== 'false') {
|
|
340
|
-
this.ignoredDevices.push(id)
|
|
341
|
-
}
|
|
342
|
-
break
|
|
343
|
-
case 'label':
|
|
344
|
-
if (typeof v !== 'string' || v === '') {
|
|
345
|
-
logIgnore(`${key}.${id}.${k}`)
|
|
346
|
-
} else {
|
|
347
|
-
this.deviceConf[id][k] = v
|
|
348
|
-
}
|
|
349
|
-
break
|
|
350
|
-
default:
|
|
351
|
-
logRemove(`${key}.${id}.${k}`)
|
|
352
|
-
}
|
|
353
|
-
})
|
|
354
|
-
})
|
|
355
|
-
} else {
|
|
356
|
-
logIgnore(key)
|
|
357
|
-
}
|
|
358
|
-
break
|
|
359
|
-
case 'name':
|
|
360
|
-
case 'platform':
|
|
361
|
-
break
|
|
362
|
-
default:
|
|
363
|
-
logRemove(key)
|
|
364
|
-
break
|
|
365
|
-
}
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
|
-
|
|
369
108
|
async pluginSetup() {
|
|
370
109
|
// Plugin has finished initialising so now onto setup
|
|
371
110
|
try {
|
|
@@ -604,192 +343,45 @@ export default class {
|
|
|
604
343
|
throwOnTimeout: true,
|
|
605
344
|
})
|
|
606
345
|
|
|
607
|
-
//
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (!httpDevice.device.includes(':')) {
|
|
619
|
-
// Eg converts abcd1234abcd1234 to AB:CD:12:34:AB:CD:12:34
|
|
620
|
-
// For sensors with an add-on sensor like H5178
|
|
621
|
-
// Eg converts abcd1234abcd1234_1 to AB:CD:12:34:AB:CD:12:34_1
|
|
622
|
-
httpDevice.device = httpDevice.device.replace(DEVICE_ID_FORMAT_REGEX, '$&:').toUpperCase()
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Check it's not a user-ignored device
|
|
626
|
-
if (this.ignoredDevices.includes(httpDevice.device)) {
|
|
627
|
-
return
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Skip Govee app device groups as these are not real devices (#1309)
|
|
631
|
-
if (['BaseGroup', 'SameModeGroup'].includes(httpDevice.sku)) {
|
|
632
|
-
this.log.debug('[%s] %s.', httpDevice.deviceName, platformLang.devIsGroup)
|
|
633
|
-
return
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Check it's not a matter-ignored device, if the config has been set
|
|
637
|
-
if (platformConsts.matterModels.includes(httpDevice.sku) && this.config.ignoreMatter) {
|
|
638
|
-
return
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Sets the flag to see if we need to set up the BLE/HTTP syncs
|
|
642
|
-
if (platformConsts.models.sensorLeak.includes(httpDevice.sku)) {
|
|
643
|
-
httpSyncNeeded = true
|
|
644
|
-
}
|
|
645
|
-
if (platformConsts.models.sensorThermo.includes(httpDevice.sku)) {
|
|
646
|
-
bleSyncNeeded = true
|
|
647
|
-
httpSyncNeeded = true
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// Find any matching device from the LAN client
|
|
651
|
-
const lanDevice = lanDevices.find(el => el.device === httpDevice.device)
|
|
652
|
-
|
|
653
|
-
// Find any matching device from the OpenAPI client to merge capabilities
|
|
654
|
-
const matchingOpenApiDevice = openApiDevices.find(el => el.device === httpDevice.device)
|
|
655
|
-
const openApiMerge = matchingOpenApiDevice
|
|
656
|
-
? {
|
|
657
|
-
openApiInfo: matchingOpenApiDevice.openApiInfo,
|
|
658
|
-
properties: matchingOpenApiDevice.properties,
|
|
659
|
-
supportCmds: matchingOpenApiDevice.supportCmds,
|
|
660
|
-
}
|
|
661
|
-
: {}
|
|
662
|
-
|
|
663
|
-
if (lanDevice) {
|
|
664
|
-
// Device exists in LAN data so add the http info to the object and initialise
|
|
665
|
-
this.initialiseDevice({
|
|
666
|
-
...lanDevice,
|
|
667
|
-
httpInfo: httpDevice,
|
|
668
|
-
model: httpDevice.sku,
|
|
669
|
-
deviceName: httpDevice.deviceName,
|
|
670
|
-
isLanDevice: true,
|
|
671
|
-
...openApiMerge,
|
|
672
|
-
})
|
|
673
|
-
lanDevicesWereInitialised = true
|
|
674
|
-
lanDevice.initialised = true
|
|
675
|
-
} else {
|
|
676
|
-
// Device doesn't exist in LAN data, but try to initialise as could be other device type
|
|
677
|
-
this.initialiseDevice({
|
|
678
|
-
device: httpDevice.device,
|
|
679
|
-
deviceName: httpDevice.deviceName,
|
|
680
|
-
model: httpDevice.sku,
|
|
681
|
-
httpInfo: httpDevice,
|
|
682
|
-
...openApiMerge,
|
|
683
|
-
})
|
|
684
|
-
}
|
|
685
|
-
httpDevicesWereInitialised = true
|
|
686
|
-
})
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if (openApiDevices && openApiDevices.length > 0) {
|
|
690
|
-
openApiDevices.forEach((openApiDevice) => {
|
|
691
|
-
// Skip Govee app device groups as these are not real devices (#1309)
|
|
692
|
-
if (['BaseGroup', 'SameModeGroup'].includes(openApiDevice.model)) {
|
|
693
|
-
this.log.debug('[%s] %s.', openApiDevice.deviceName, platformLang.devIsGroup)
|
|
694
|
-
return
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
if (this.ignoredDevices.includes(openApiDevice.device)) {
|
|
698
|
-
return
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
if (platformConsts.matterModels.includes(openApiDevice.model) && this.config.ignoreMatter) {
|
|
702
|
-
return
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
if (httpDevices.some(httpDevice => httpDevice.device === openApiDevice.device)) {
|
|
706
|
-
return
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
const lanDevice = lanDevices.find(el => el.device === openApiDevice.device)
|
|
710
|
-
|
|
711
|
-
if (lanDevice) {
|
|
712
|
-
this.initialiseDevice({
|
|
713
|
-
...lanDevice,
|
|
714
|
-
deviceName: openApiDevice.deviceName,
|
|
715
|
-
model: openApiDevice.model,
|
|
716
|
-
openApiInfo: openApiDevice.openApiInfo,
|
|
717
|
-
properties: openApiDevice.properties,
|
|
718
|
-
supportCmds: openApiDevice.supportCmds,
|
|
719
|
-
isLanDevice: true,
|
|
720
|
-
})
|
|
721
|
-
lanDevicesWereInitialised = true
|
|
722
|
-
lanDevice.initialised = true
|
|
723
|
-
} else {
|
|
724
|
-
this.initialiseDevice(openApiDevice)
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
openApiDevicesWereInitialised = true
|
|
728
|
-
})
|
|
729
|
-
}
|
|
346
|
+
// Work out what to set up. Govee describes the same device through up to
|
|
347
|
+
// three lists, and how they combine is decided away from here so it can
|
|
348
|
+
// be tested rather than hoped about
|
|
349
|
+
const plan = planDeviceSetup({
|
|
350
|
+
httpDevices,
|
|
351
|
+
lanDevices,
|
|
352
|
+
openApiDevices,
|
|
353
|
+
ignoredDevices: this.ignoredDevices,
|
|
354
|
+
ignoreMatter: this.config.ignoreMatter,
|
|
355
|
+
deviceConf: this.deviceConf,
|
|
356
|
+
})
|
|
730
357
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if (pendingLANDevices.length > 0) {
|
|
734
|
-
// No devices from HTTP client, but LAN devices exist
|
|
735
|
-
pendingLANDevices.forEach((lanDevice) => {
|
|
736
|
-
// Check it's not a user-ignored device
|
|
737
|
-
if (this.ignoredDevices.includes(lanDevice.device)) {
|
|
738
|
-
return
|
|
739
|
-
}
|
|
358
|
+
plan.skippedGroups.forEach(name => this.log.debug('[%s] %s.', name, platformLang.devIsGroup))
|
|
359
|
+
plan.devices.forEach(device => this.initialiseDevice(device))
|
|
740
360
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
361
|
+
const {
|
|
362
|
+
bleSyncNeeded,
|
|
363
|
+
httpSyncNeeded,
|
|
364
|
+
httpDevicesWereInitialised,
|
|
365
|
+
openApiDevicesWereInitialised,
|
|
366
|
+
lanDevicesWereInitialised,
|
|
367
|
+
} = plan
|
|
748
368
|
|
|
749
|
-
|
|
750
|
-
// Since LAN does not provide a name, we will use the configured label or device id
|
|
751
|
-
this.initialiseDevice({
|
|
752
|
-
device: lanDevice.device,
|
|
753
|
-
deviceName: this.deviceConf?.[lanDevice.device]?.label || lanDevice.device.replaceAll(':', ''),
|
|
754
|
-
model: lanDevice.sku || 'HXXXX', // In case the model is not provided
|
|
755
|
-
isLanDevice: true,
|
|
756
|
-
isLanOnly: true,
|
|
757
|
-
})
|
|
758
|
-
lanDevicesWereInitialised = true
|
|
759
|
-
})
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
if (!lanDevicesWereInitialised && !httpDevicesWereInitialised && !openApiDevicesWereInitialised) {
|
|
763
|
-
// No devices either from HTTP client nor LAN client
|
|
369
|
+
if (!plan.anyInitialised) {
|
|
764
370
|
throw new Error(platformLang.noDevs)
|
|
765
371
|
}
|
|
766
372
|
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|| (this.config.ignoreMatter && platformConsts.matterModels.includes(accessory.context.gvModel))
|
|
780
|
-
|
|
781
|
-
// Also remove it if the device no longer exists in Govee, but never based on
|
|
782
|
-
// an incomplete cloud list, and never for LAN-only devices which may simply
|
|
783
|
-
// have missed a scan window (#1264)
|
|
784
|
-
const isMissing = !httpDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
785
|
-
&& !lanDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
786
|
-
&& !openApiDevices.some(el => el.device === accessory.context.gvDeviceId)
|
|
787
|
-
const isLanOnly = accessory.context.hasLanControl && !accessory.context.firmware
|
|
788
|
-
|
|
789
|
-
if (isIgnored || (isMissing && cloudListsFetched && !isLanOnly)) {
|
|
790
|
-
this.removeAccessory(accessory)
|
|
791
|
-
}
|
|
792
|
-
})
|
|
373
|
+
// Let go of any cached accessory that no longer belongs. Removing one
|
|
374
|
+
// destroys the HomeKit scenes and automations it is part of, so the rules
|
|
375
|
+
// for this live next to their tests
|
|
376
|
+
planRedundantAccessories({
|
|
377
|
+
accessories: [...devicesInHB.values()],
|
|
378
|
+
httpDevices,
|
|
379
|
+
lanDevices,
|
|
380
|
+
openApiDevices,
|
|
381
|
+
ignoredDevices: this.ignoredDevices,
|
|
382
|
+
ignoreMatter: this.config.ignoreMatter,
|
|
383
|
+
cloudListsFetched: cloudListsWereFetched(this.config, this),
|
|
384
|
+
}).forEach(accessory => this.removeAccessory(accessory))
|
|
793
385
|
|
|
794
386
|
// Set up the ble client sync needed for thermo sensor devices
|
|
795
387
|
if (bleSyncNeeded) {
|
|
@@ -1094,204 +686,14 @@ export default class {
|
|
|
1094
686
|
try {
|
|
1095
687
|
const deviceConf = this.deviceConf[device.device.toUpperCase()] || {}
|
|
1096
688
|
const uuid = this.api.hap.uuid.generate(device.device)
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1106
|
-
} else if (platformConsts.models.switchSingle.includes(device.model)) {
|
|
1107
|
-
// Device is a cloud enabled Wi-Fi switch
|
|
1108
|
-
switch (deviceConf.showAs || platformConsts.defaultValues.showAs) {
|
|
1109
|
-
case 'audio': {
|
|
1110
|
-
if (devicesInHB.get(uuid)) {
|
|
1111
|
-
this.removeAccessory(devicesInHB.get(uuid))
|
|
1112
|
-
}
|
|
1113
|
-
devInstance = deviceTypes.deviceTVSingle
|
|
1114
|
-
accessory = this.addExternalAccessory(device, 34)
|
|
1115
|
-
break
|
|
1116
|
-
}
|
|
1117
|
-
case 'box': {
|
|
1118
|
-
if (devicesInHB.get(uuid)) {
|
|
1119
|
-
this.removeAccessory(devicesInHB.get(uuid))
|
|
1120
|
-
}
|
|
1121
|
-
devInstance = deviceTypes.deviceTVSingle
|
|
1122
|
-
accessory = this.addExternalAccessory(device, 35)
|
|
1123
|
-
break
|
|
1124
|
-
}
|
|
1125
|
-
case 'stick': {
|
|
1126
|
-
if (devicesInHB.get(uuid)) {
|
|
1127
|
-
this.removeAccessory(devicesInHB.get(uuid))
|
|
1128
|
-
}
|
|
1129
|
-
devInstance = deviceTypes.deviceTVSingle
|
|
1130
|
-
accessory = this.addExternalAccessory(device, 36)
|
|
1131
|
-
break
|
|
1132
|
-
}
|
|
1133
|
-
case 'cooler': {
|
|
1134
|
-
if (!deviceConf.temperatureSource) {
|
|
1135
|
-
this.log.warn('[%s] %s.', device.deviceName, platformLang.heaterSimNoSensor)
|
|
1136
|
-
if (devicesInHB.has(uuid)) {
|
|
1137
|
-
this.removeAccessory(devicesInHB.get(uuid))
|
|
1138
|
-
}
|
|
1139
|
-
return
|
|
1140
|
-
}
|
|
1141
|
-
devInstance = deviceTypes.deviceCoolerSingle
|
|
1142
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1143
|
-
break
|
|
1144
|
-
}
|
|
1145
|
-
case 'heater': {
|
|
1146
|
-
if (!deviceConf.temperatureSource) {
|
|
1147
|
-
this.log.warn('[%s] %s.', device.deviceName, platformLang.heaterSimNoSensor)
|
|
1148
|
-
if (devicesInHB.has(uuid)) {
|
|
1149
|
-
this.removeAccessory(devicesInHB.get(uuid))
|
|
1150
|
-
}
|
|
1151
|
-
return
|
|
1152
|
-
}
|
|
1153
|
-
devInstance = deviceTypes.deviceHeater2Single
|
|
1154
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1155
|
-
break
|
|
1156
|
-
}
|
|
1157
|
-
case 'purifier': {
|
|
1158
|
-
devInstance = deviceTypes.devicePurifierSingle
|
|
1159
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1160
|
-
break
|
|
1161
|
-
}
|
|
1162
|
-
case 'switch': {
|
|
1163
|
-
devInstance = deviceTypes.deviceSwitchSingle
|
|
1164
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1165
|
-
break
|
|
1166
|
-
}
|
|
1167
|
-
case 'tap': {
|
|
1168
|
-
devInstance = deviceTypes.deviceTapSingle
|
|
1169
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1170
|
-
break
|
|
1171
|
-
}
|
|
1172
|
-
case 'valve': {
|
|
1173
|
-
devInstance = deviceTypes.deviceValveSingle
|
|
1174
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1175
|
-
break
|
|
1176
|
-
}
|
|
1177
|
-
default:
|
|
1178
|
-
devInstance = deviceTypes.deviceOutletSingle
|
|
1179
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1180
|
-
break
|
|
1181
|
-
}
|
|
1182
|
-
} else if (platformConsts.models.switchDouble.includes(device.model)) {
|
|
1183
|
-
// Device is an AWS enabled Wi-Fi double switch
|
|
1184
|
-
switch (deviceConf.showAs || platformConsts.defaultValues.showAs) {
|
|
1185
|
-
case 'switch': {
|
|
1186
|
-
devInstance = deviceTypes.deviceSwitchDouble
|
|
1187
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1188
|
-
break
|
|
1189
|
-
}
|
|
1190
|
-
default: {
|
|
1191
|
-
devInstance = deviceTypes.deviceOutletDouble
|
|
1192
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1193
|
-
break
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
} else if (platformConsts.models.switchTriple.includes(device.model)) {
|
|
1197
|
-
// Device is an AWS enabled Wi-Fi double switch
|
|
1198
|
-
switch (deviceConf.showAs || platformConsts.defaultValues.showAs) {
|
|
1199
|
-
case 'switch': {
|
|
1200
|
-
devInstance = deviceTypes.deviceSwitchTriple
|
|
1201
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1202
|
-
break
|
|
1203
|
-
}
|
|
1204
|
-
default: {
|
|
1205
|
-
devInstance = deviceTypes.deviceOutletTriple
|
|
1206
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1207
|
-
break
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
} else if (platformConsts.models.sensorLeak.includes(device.model)) {
|
|
1211
|
-
// Device is a leak sensor
|
|
1212
|
-
devInstance = deviceTypes.deviceSensorLeak
|
|
1213
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1214
|
-
} else if (platformConsts.models.sensorPresence.includes(device.model)) {
|
|
1215
|
-
// Device is a presence sensor
|
|
1216
|
-
devInstance = deviceTypes.deviceSensorPresence
|
|
1217
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1218
|
-
} else if (platformConsts.models.sensorThermo.includes(device.model)) {
|
|
1219
|
-
// Device is a thermo-hygrometer sensor
|
|
1220
|
-
devInstance = deviceConf.showExtraSwitch
|
|
1221
|
-
? deviceTypes.deviceSensorThermoSwitch
|
|
1222
|
-
: deviceTypes.deviceSensorThermo
|
|
1223
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1224
|
-
} else if (platformConsts.models.sensorThermo4.includes(device.model)) {
|
|
1225
|
-
// Device is a thermo-hygrometer sensor with 4 prongs and AWS support
|
|
1226
|
-
devInstance = deviceTypes.deviceSensorThermo4
|
|
1227
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1228
|
-
} else if (platformConsts.models.sensorMonitor.includes(device.model)) {
|
|
1229
|
-
devInstance = deviceTypes.deviceSensorMonitor
|
|
1230
|
-
doAWSPolling = true
|
|
1231
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1232
|
-
} else if (platformConsts.models.sensorCO2.includes(device.model)) {
|
|
1233
|
-
// Device is a CO2 + temperature + humidity monitor (H5140)
|
|
1234
|
-
devInstance = deviceTypes.deviceSensorCO2
|
|
1235
|
-
doAWSPolling = true
|
|
1236
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1237
|
-
} else if (platformConsts.models.fan.includes(device.model)) {
|
|
1238
|
-
// Device is a fan
|
|
1239
|
-
devInstance = deviceTypes[`deviceFan${device.model}`]
|
|
1240
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1241
|
-
} else if (platformConsts.models.heater1.includes(device.model)) {
|
|
1242
|
-
// Device is a H7130
|
|
1243
|
-
devInstance = deviceConf.tempReporting
|
|
1244
|
-
? deviceTypes.deviceHeater1B
|
|
1245
|
-
: deviceTypes.deviceHeater1A
|
|
1246
|
-
doAWSPolling = true
|
|
1247
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1248
|
-
} else if (platformConsts.models.heater2.includes(device.model)) {
|
|
1249
|
-
// Device is a H7131/H7132
|
|
1250
|
-
devInstance = deviceTypes.deviceHeater2
|
|
1251
|
-
doAWSPolling = true
|
|
1252
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1253
|
-
} else if (platformConsts.models.humidifier.includes(device.model)) {
|
|
1254
|
-
// Device is a humidifier
|
|
1255
|
-
doAWSPolling = true
|
|
1256
|
-
devInstance = deviceTypes[`deviceHumidifier${device.model}`]
|
|
1257
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1258
|
-
} else if (platformConsts.models.dehumidifier.includes(device.model)) {
|
|
1259
|
-
// Device is a dehumidifier
|
|
1260
|
-
devInstance = deviceTypes[`deviceDehumidifier${device.model}`]
|
|
1261
|
-
doAWSPolling = true
|
|
1262
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1263
|
-
} else if (platformConsts.models.purifier.includes(device.model)) {
|
|
1264
|
-
// Device is a purifier
|
|
1265
|
-
devInstance = deviceTypes[`devicePurifier${device.model}`]
|
|
1266
|
-
doAWSPolling = true
|
|
1267
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1268
|
-
} else if (platformConsts.models.diffuser.includes(device.model)) {
|
|
1269
|
-
// Device is a diffuser
|
|
1270
|
-
devInstance = deviceTypes[`deviceDiffuser${device.model}`]
|
|
1271
|
-
doAWSPolling = true
|
|
1272
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1273
|
-
} else if (platformConsts.models.sensorButton.includes(device.model)) {
|
|
1274
|
-
// Device is a button
|
|
1275
|
-
devInstance = deviceTypes.deviceSensorButton
|
|
1276
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1277
|
-
} else if (platformConsts.models.sensorContact.includes(device.model)) {
|
|
1278
|
-
// Device is a contact sensor
|
|
1279
|
-
devInstance = deviceTypes.deviceSensorContact
|
|
1280
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1281
|
-
} else if (platformConsts.models.kettle.includes(device.model)) {
|
|
1282
|
-
// Device is a kettle
|
|
1283
|
-
devInstance = deviceTypes.deviceKettle
|
|
1284
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1285
|
-
} else if (platformConsts.models.iceMaker.includes(device.model)) {
|
|
1286
|
-
// Device is an ice maker
|
|
1287
|
-
devInstance = deviceTypes[`deviceIceMaker${device.model}`]
|
|
1288
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1289
|
-
} else if (platformConsts.models.template.includes(device.model)) {
|
|
1290
|
-
// Device is a work-in-progress
|
|
1291
|
-
devInstance = deviceTypes.deviceTemplate
|
|
1292
|
-
accessory = devicesInHB.get(uuid) || this.addAccessory(device)
|
|
1293
|
-
} else {
|
|
1294
|
-
// Device is not in any supported model list but could be implemented into the plugin
|
|
689
|
+
const cached = devicesInHB.get(uuid)
|
|
690
|
+
|
|
691
|
+
// Which handler this device gets is decided by the dispatch table, so
|
|
692
|
+
// adding a device type does not mean editing this file
|
|
693
|
+
const route = resolveDeviceType(device, deviceConf, cached?.context)
|
|
694
|
+
|
|
695
|
+
if (!route) {
|
|
696
|
+
// Not in any supported model list, but it could be added to the plugin
|
|
1295
697
|
this.log.warn(
|
|
1296
698
|
'[%s] %s:\n%s',
|
|
1297
699
|
device.deviceName,
|
|
@@ -1301,169 +703,44 @@ export default class {
|
|
|
1301
703
|
return
|
|
1302
704
|
}
|
|
1303
705
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
706
|
+
if (route.skip === 'needsTemperatureSource') {
|
|
707
|
+
// Standing in for a heater or cooler needs a temperature from somewhere
|
|
708
|
+
// else, and without one there is nothing to show
|
|
709
|
+
this.log.warn('[%s] %s.', device.deviceName, platformLang.heaterSimNoSensor)
|
|
710
|
+
if (cached) {
|
|
711
|
+
this.removeAccessory(cached)
|
|
712
|
+
}
|
|
713
|
+
return
|
|
1307
714
|
}
|
|
1308
715
|
|
|
1309
|
-
|
|
1310
|
-
this.applyAccessoryLogging(accessory)
|
|
1311
|
-
|
|
1312
|
-
// Add the temperatureSource config to the context if exists
|
|
1313
|
-
if (deviceConf.temperatureSource) {
|
|
1314
|
-
accessory.context.temperatureSource = deviceConf.temperatureSource
|
|
1315
|
-
}
|
|
716
|
+
const devInstance = route.handler
|
|
1316
717
|
|
|
1317
|
-
|
|
1318
|
-
if (
|
|
1319
|
-
accessory
|
|
718
|
+
let accessory
|
|
719
|
+
if (route.externalCategory) {
|
|
720
|
+
// A television has to be published as its own accessory rather than as
|
|
721
|
+
// a tile on the bridge, so any tile from a previous setting goes first
|
|
722
|
+
if (cached) {
|
|
723
|
+
this.removeAccessory(cached)
|
|
724
|
+
}
|
|
725
|
+
accessory = this.addExternalAccessory(device, route.externalCategory)
|
|
1320
726
|
} else {
|
|
1321
|
-
|
|
727
|
+
accessory = cached || this.addAccessory(device)
|
|
1322
728
|
}
|
|
1323
729
|
|
|
1324
|
-
//
|
|
1325
|
-
if (
|
|
1326
|
-
|
|
1327
|
-
accessory.context.supportedCmdsOpts = {}
|
|
1328
|
-
|
|
1329
|
-
device.supportCmds.forEach((cmd) => {
|
|
1330
|
-
if (device?.properties?.[cmd]) {
|
|
1331
|
-
accessory.context.supportedCmdsOpts[cmd] = device.properties[cmd]
|
|
1332
|
-
}
|
|
1333
|
-
})
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// Add some initial context information which is changed later
|
|
1337
|
-
accessory.context.hasAwsControl = false
|
|
1338
|
-
accessory.context.useAwsControl = false
|
|
1339
|
-
accessory.context.hasOpenApiControl = false
|
|
1340
|
-
accessory.context.useOpenApiControl = false
|
|
1341
|
-
accessory.context.hasBleControl = false
|
|
1342
|
-
accessory.context.useBleControl = false
|
|
1343
|
-
accessory.context.hasLanControl = device.isLanDevice
|
|
1344
|
-
accessory.context.useLanControl = accessory.context.hasLanControl
|
|
1345
|
-
accessory.context.firmware = false
|
|
1346
|
-
accessory.context.hardware = false
|
|
1347
|
-
accessory.context.image = false
|
|
1348
|
-
|
|
1349
|
-
// Overrides for when a custom IP is provided, for a light which is not BLE only
|
|
1350
|
-
if (
|
|
1351
|
-
deviceConf.customIPAddress
|
|
1352
|
-
&& accessory.context.hasLanControl
|
|
1353
|
-
&& accessory.context.hasAwsControl
|
|
1354
|
-
&& platformConsts.models.rgb.includes(device.model)
|
|
1355
|
-
) {
|
|
1356
|
-
accessory.context.hasLanControl = true
|
|
1357
|
-
accessory.context.useLanControl = true
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// If the device is LAN-only, then sync the display name with the label in the configuration
|
|
1361
|
-
if (device.isLanOnly) {
|
|
1362
|
-
accessory.displayName = device.deviceName
|
|
730
|
+
// Final check the accessory now exists in Homebridge
|
|
731
|
+
if (!accessory) {
|
|
732
|
+
throw new Error(platformLang.accNotFound)
|
|
1363
733
|
}
|
|
1364
734
|
|
|
1365
|
-
//
|
|
1366
|
-
|
|
1367
|
-
// Save the hardware and firmware versions
|
|
1368
|
-
accessory.context.firmware = device.httpInfo.versionSoft
|
|
1369
|
-
accessory.context.hardware = device.httpInfo.versionHard
|
|
1370
|
-
|
|
1371
|
-
// It's possible to show a nice little icon of the device in the Homebridge UI
|
|
1372
|
-
if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.extResources) {
|
|
1373
|
-
try {
|
|
1374
|
-
const parsed = JSON.parse(device.httpInfo.deviceExt.extResources)
|
|
1375
|
-
if (parsed && parsed.skuUrl) {
|
|
1376
|
-
accessory.context.image = parsed.skuUrl
|
|
1377
|
-
}
|
|
1378
|
-
} catch {
|
|
1379
|
-
// Ignore malformed extResources
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// HTTP info lets us see if AWS/BLE connection methods are available
|
|
1384
|
-
if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.deviceSettings) {
|
|
1385
|
-
let parsed
|
|
1386
|
-
try {
|
|
1387
|
-
parsed = JSON.parse(device.httpInfo.deviceExt.deviceSettings)
|
|
1388
|
-
} catch {
|
|
1389
|
-
this.log.debugWarn('[%s] failed to parse deviceSettings, skipping AWS/BLE setup.', accessory.displayName)
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
// Check to see if AWS is possible
|
|
1393
|
-
if (parsed) {
|
|
1394
|
-
if (parsed.topic) {
|
|
1395
|
-
accessory.context.hasAwsControl = true
|
|
1396
|
-
accessory.context.awsTopic = parsed.topic
|
|
1397
|
-
|
|
1398
|
-
if (this.awsClient) {
|
|
1399
|
-
accessory.context.useAwsControl = true
|
|
1400
|
-
accessory.context.awsBrightnessNoScale = deviceConf.awsBrightnessNoScale
|
|
1401
|
-
accessory.context.awsColourMode = deviceConf.awsColourMode || platformConsts.defaultValues.awsColourMode
|
|
1402
|
-
awsDevices.push(device.device)
|
|
1403
|
-
|
|
1404
|
-
// Certain models need AWS polling
|
|
1405
|
-
if (doAWSPolling) {
|
|
1406
|
-
awsDevicesToPoll.push(device.device)
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// Check to see if BLE is possible
|
|
1412
|
-
if (parsed.bleName) {
|
|
1413
|
-
const providedBle = parsed.address ? parsed.address.toLowerCase() : device.device.substring(6).toLowerCase()
|
|
1414
|
-
accessory.context.hasBleControl = !!parsed.bleName
|
|
1415
|
-
accessory.context.bleAddress = deviceConf.customAddress
|
|
1416
|
-
? deviceConf.customAddress.toLowerCase()
|
|
1417
|
-
: providedBle
|
|
1418
|
-
accessory.context.bleName = parsed.bleName
|
|
1419
|
-
accessory.context.bleWriteUuid = getDeviceCapabilities(accessory.context.gvModel).bleWriteUuid
|
|
1420
|
-
if (this.bleClient) {
|
|
1421
|
-
accessory.context.useBleControl = true
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
// Gateway subdevices (e.g. leak sensors paired to an H5044) include
|
|
1426
|
-
// their gateway id and slot number, which we need to match real-time
|
|
1427
|
-
// leak events relayed by the gateway over AWS (#1276)
|
|
1428
|
-
if (hasProperty(parsed, 'sno') && parsed.gatewayInfo?.device) {
|
|
1429
|
-
accessory.context.gatewaySno = parsed.sno
|
|
1430
|
-
accessory.context.gatewayDevice = parsed.gatewayInfo.device
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
// Get a min and max temperature/humidity range to show in the homebridge-ui
|
|
1434
|
-
if (hasProperty(parsed, 'temCali')) {
|
|
1435
|
-
accessory.context.minTemp = parsed.temMin / 100
|
|
1436
|
-
accessory.context.maxTemp = parsed.temMax / 100
|
|
1437
|
-
accessory.context.offTemp = parsed.temCali
|
|
1438
|
-
}
|
|
1439
|
-
if (hasProperty(parsed, 'humCali')) {
|
|
1440
|
-
accessory.context.minHumi = parsed.humMin / 100
|
|
1441
|
-
accessory.context.maxHumi = parsed.humMax / 100
|
|
1442
|
-
accessory.context.offHumi = parsed.humCali
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
735
|
+
// Set the logging level for this device
|
|
736
|
+
this.applyAccessoryLogging(accessory)
|
|
1447
737
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
// Log what Govee says this device can do. Working out whether a model
|
|
1454
|
-
// exposes something like a night light otherwise means reverse
|
|
1455
|
-
// engineering its status messages, when the answer is right here
|
|
1456
|
-
const instances = Object.entries(accessory.context.openApiCapabilities)
|
|
1457
|
-
.map(([instance, capability]) => `${instance} (${capability?.type ?? 'unknown type'})`)
|
|
1458
|
-
this.log.debug(
|
|
1459
|
-
'[%s] openapi category [%s] capabilities [%s].',
|
|
1460
|
-
accessory.displayName,
|
|
1461
|
-
device.openApiInfo.category || 'unknown',
|
|
1462
|
-
instances.join(', ') || 'none',
|
|
1463
|
-
)
|
|
1464
|
-
}
|
|
738
|
+
applyDeviceContext(this, accessory, device, deviceConf, {
|
|
739
|
+
awsDevices,
|
|
740
|
+
awsDevicesToPoll,
|
|
741
|
+
doAWSPolling: route.awsPolling,
|
|
742
|
+
})
|
|
1465
743
|
|
|
1466
|
-
// Create the instance for this device type
|
|
1467
744
|
accessory.control = new devInstance(this, accessory)
|
|
1468
745
|
|
|
1469
746
|
// Log the device initialisation
|
|
@@ -2077,7 +1354,14 @@ export default class {
|
|
|
2077
1354
|
this.log.warn('[%s] %s %s.', accessory.displayName, platformLang.devNotUpdated, parseError(err))
|
|
2078
1355
|
}
|
|
2079
1356
|
} else {
|
|
2080
|
-
|
|
1357
|
+
// Nothing in the payload was understood. This is the most useful thing a
|
|
1358
|
+
// user can send in when asking for a device or a function to be added, so
|
|
1359
|
+
// it is reported in full rather than hidden behind debug logging.
|
|
1360
|
+
logUnknownData(accessory, {
|
|
1361
|
+
kind: 'payload',
|
|
1362
|
+
source: params.source,
|
|
1363
|
+
raw: params,
|
|
1364
|
+
})
|
|
2081
1365
|
}
|
|
2082
1366
|
}
|
|
2083
1367
|
}
|