@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
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a device can do, worked out from several sources rather than one table.
|
|
3
|
+
*
|
|
4
|
+
* Writing out all six hundred models by hand is a job nobody finishes, and it
|
|
5
|
+
* throws away the two things we already have: Govee's own API says what each
|
|
6
|
+
* device can do, and some behaviour depends on firmware rather than model. So
|
|
7
|
+
* instead of a table this asks a series of layers and takes the first answer:
|
|
8
|
+
*
|
|
9
|
+
* 1. user config an explicit setting always wins
|
|
10
|
+
* 2. firmware this model, at or above this firmware version
|
|
11
|
+
* 3. model written down because someone worked it out
|
|
12
|
+
* 4. discovered what this particular device reported it can do
|
|
13
|
+
* 5. family what this kind of appliance usually has
|
|
14
|
+
*
|
|
15
|
+
* Most models never need an entry of their own. They are answered by what the
|
|
16
|
+
* device reported plus the family defaults, which is why this scales to models
|
|
17
|
+
* nobody has written down.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ Family defaults are deliberately generous. A control this plugin has
|
|
20
|
+
* offered for years may be in use by someone, and taking it away to tidy up
|
|
21
|
+
* would break a working setup. Only a model entry - written after someone
|
|
22
|
+
* confirmed it - is allowed to say a device does not have something.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { compareVersions } from './device-capabilities.js'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What each kind of appliance usually offers.
|
|
29
|
+
*
|
|
30
|
+
* These are the fallback, so they describe the group rather than any one model.
|
|
31
|
+
* Where a group has historically been given a control, it stays here even if
|
|
32
|
+
* some members turn out not to have it - see the warning above.
|
|
33
|
+
*/
|
|
34
|
+
const familyControls = {
|
|
35
|
+
heater1: { swing: true, lock: true, light: false },
|
|
36
|
+
heater2: { swing: true, lock: true, light: true },
|
|
37
|
+
fan: { swing: true, lock: false, light: false },
|
|
38
|
+
humidifier: { swing: false, lock: false, light: false },
|
|
39
|
+
dehumidifier: { swing: false, lock: false, light: false },
|
|
40
|
+
purifier: { swing: false, lock: false, light: false },
|
|
41
|
+
iceMaker: { swing: false, lock: false, light: false },
|
|
42
|
+
diffuser: { swing: false, lock: false, light: false },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const emptyControls = {
|
|
46
|
+
swing: false,
|
|
47
|
+
lock: false,
|
|
48
|
+
light: false,
|
|
49
|
+
humiditySensor: false,
|
|
50
|
+
temperatureSensor: false,
|
|
51
|
+
modes: false,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Controls worked out for a particular model.
|
|
56
|
+
*
|
|
57
|
+
* Only add a model here once its controls are actually known. An entry is
|
|
58
|
+
* trusted over what the device reports, and it is the only thing allowed to
|
|
59
|
+
* take a control away.
|
|
60
|
+
*/
|
|
61
|
+
const modelControls = {
|
|
62
|
+
// Heaters. These match what Govee's own app offers for each model.
|
|
63
|
+
// Added new, so there is nobody whose setup could be broken by them
|
|
64
|
+
H7137: { swing: false, lock: false, light: false },
|
|
65
|
+
H713E: { swing: false, lock: false, light: false },
|
|
66
|
+
|
|
67
|
+
// Diffusers are power only, which is what the plugin already offers
|
|
68
|
+
H7161: { swing: false, lock: false, light: false },
|
|
69
|
+
H7162: { swing: false, lock: false, light: false },
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Controls a model is believed NOT to have, which are still being offered.
|
|
74
|
+
*
|
|
75
|
+
* These are recorded rather than applied. Each has been offered for a long
|
|
76
|
+
* time, so an owner may be relying on it, and taking it away to match a
|
|
77
|
+
* capability list would break a working setup for the sake of tidiness. The
|
|
78
|
+
* evidence is written down here so nobody has to work it out twice, and so
|
|
79
|
+
* there is a list to check the next time an owner of one of these gets in
|
|
80
|
+
* touch.
|
|
81
|
+
*
|
|
82
|
+
* Move an entry into `modelControls` once an owner confirms the control really
|
|
83
|
+
* does nothing.
|
|
84
|
+
*/
|
|
85
|
+
const unconfirmedMissing = {
|
|
86
|
+
H713A: { swing: false },
|
|
87
|
+
H713B: { swing: false },
|
|
88
|
+
H713C: { swing: false },
|
|
89
|
+
H7135: { swing: false, light: false },
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Controls that only appear at or above a firmware version. Same shape as the
|
|
94
|
+
* protocol overrides next door: a list per model, oldest rule first.
|
|
95
|
+
*/
|
|
96
|
+
const firmwareControls = {}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* What Govee's API calls a capability, and which control it means to us.
|
|
100
|
+
*
|
|
101
|
+
* These names come back per device, so they say what this particular unit can
|
|
102
|
+
* do rather than what its model generally can. That makes them the only source
|
|
103
|
+
* here that works for a model nobody has written down.
|
|
104
|
+
*/
|
|
105
|
+
const discoveryMap = {
|
|
106
|
+
oscillationToggle: 'swing',
|
|
107
|
+
lockToggle: 'lock',
|
|
108
|
+
nightlightToggle: 'light',
|
|
109
|
+
mainLightToggle: 'light',
|
|
110
|
+
backgroundLightToggle: 'light',
|
|
111
|
+
sensorHumidity: 'humiditySensor',
|
|
112
|
+
sensorTemperature: 'temperatureSensor',
|
|
113
|
+
workMode: 'modes',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Turn the capability list a device reported into controls.
|
|
118
|
+
*
|
|
119
|
+
* Only ever adds. A capability the device did not mention is left undecided so
|
|
120
|
+
* a later layer can answer, because the API not listing something is not proof
|
|
121
|
+
* the device cannot do it.
|
|
122
|
+
*/
|
|
123
|
+
function fromDiscovery(openApiCapabilities) {
|
|
124
|
+
const discovered = {}
|
|
125
|
+
Object.keys(openApiCapabilities || {}).forEach((instance) => {
|
|
126
|
+
const control = discoveryMap[instance]
|
|
127
|
+
if (control) {
|
|
128
|
+
discovered[control] = true
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
return discovered
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Config settings that name a control directly, so an owner can turn something
|
|
136
|
+
* off that their device does not really have without waiting for a release.
|
|
137
|
+
*/
|
|
138
|
+
function fromConfig(deviceConf) {
|
|
139
|
+
const chosen = {}
|
|
140
|
+
if (deviceConf?.hideLight === true) {
|
|
141
|
+
chosen.light = false
|
|
142
|
+
}
|
|
143
|
+
if (deviceConf?.hideSwing === true) {
|
|
144
|
+
chosen.swing = false
|
|
145
|
+
}
|
|
146
|
+
return chosen
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function firmwareLayer(model, firmware) {
|
|
150
|
+
if (!firmware) {
|
|
151
|
+
return {}
|
|
152
|
+
}
|
|
153
|
+
const applied = {}
|
|
154
|
+
const rules = [...(firmwareControls[model] || [])]
|
|
155
|
+
.sort((a, b) => compareVersions(a.minVersion, b.minVersion))
|
|
156
|
+
rules.forEach((rule) => {
|
|
157
|
+
if (compareVersions(firmware, rule.minVersion) >= 0) {
|
|
158
|
+
Object.assign(applied, rule.controls)
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
return applied
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Work out what a device can do.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} context describing the device
|
|
168
|
+
* @param {string} context.model the govee model, eg 'H7130'
|
|
169
|
+
* @param {string} [context.family] which set of defaults to fall back on
|
|
170
|
+
* @param {string} [context.firmware] the firmware version the device reported
|
|
171
|
+
* @param {object} [context.openApiCapabilities] what the api said it can do
|
|
172
|
+
* @param {object} [context.deviceConf] this device's settings
|
|
173
|
+
* @returns {{ controls: object, sources: object }} the controls, and which
|
|
174
|
+
* layer decided each one - the second is for the log, so an unexpected answer
|
|
175
|
+
* can be traced without guessing
|
|
176
|
+
*/
|
|
177
|
+
export function resolveControls(context = {}) {
|
|
178
|
+
const {
|
|
179
|
+
model,
|
|
180
|
+
family,
|
|
181
|
+
firmware,
|
|
182
|
+
openApiCapabilities,
|
|
183
|
+
deviceConf,
|
|
184
|
+
} = context
|
|
185
|
+
|
|
186
|
+
// Lowest priority first, so a later layer overwrites an earlier one
|
|
187
|
+
const layers = [
|
|
188
|
+
['family', familyControls[family] || {}],
|
|
189
|
+
['discovered', fromDiscovery(openApiCapabilities)],
|
|
190
|
+
['model', modelControls[model] || {}],
|
|
191
|
+
['firmware', firmwareLayer(model, firmware)],
|
|
192
|
+
['config', fromConfig(deviceConf)],
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
const controls = { ...emptyControls }
|
|
196
|
+
const sources = {}
|
|
197
|
+
|
|
198
|
+
layers.forEach(([name, values]) => {
|
|
199
|
+
Object.entries(values).forEach(([control, value]) => {
|
|
200
|
+
controls[control] = value
|
|
201
|
+
sources[control] = name
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
return { controls, sources }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A one-line summary of where each answer came from, for the debug log. Reading
|
|
210
|
+
* "swing from family" tells you nobody has confirmed it, which is a different
|
|
211
|
+
* situation from "swing from model".
|
|
212
|
+
*/
|
|
213
|
+
export function describeSources(sources) {
|
|
214
|
+
return Object.entries(sources)
|
|
215
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
216
|
+
.map(([control, source]) => `${control} from ${source}`)
|
|
217
|
+
.join(', ') || 'nothing known'
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export { familyControls, modelControls, unconfirmedMissing }
|
|
@@ -67,7 +67,7 @@ function buildStateDualCommand(params) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function buildStateOutletCommand(params, context) {
|
|
70
|
-
const caps = getDeviceCapabilities(context.gvModel)
|
|
70
|
+
const caps = getDeviceCapabilities(context.gvModel, context.firmware)
|
|
71
71
|
return {
|
|
72
72
|
awsParams: {
|
|
73
73
|
cmd: 'turn',
|
|
@@ -118,7 +118,7 @@ function buildOpenApiCommand(params) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
function buildBrightnessCommand(params, context) {
|
|
121
|
-
const caps = getDeviceCapabilities(context.gvModel)
|
|
121
|
+
const caps = getDeviceCapabilities(context.gvModel, context.firmware)
|
|
122
122
|
return {
|
|
123
123
|
awsParams: {
|
|
124
124
|
cmd: 'brightness',
|
|
@@ -143,7 +143,7 @@ function buildBrightnessCommand(params, context) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function buildColorCommand(params, context) {
|
|
146
|
-
const caps = getDeviceCapabilities(context.gvModel)
|
|
146
|
+
const caps = getDeviceCapabilities(context.gvModel, context.firmware)
|
|
147
147
|
const { r, g, b } = params.value
|
|
148
148
|
|
|
149
149
|
let awsParams
|
|
@@ -180,7 +180,7 @@ function buildColorCommand(params, context) {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
function buildColorTempCommand(params, context) {
|
|
183
|
-
const caps = getDeviceCapabilities(context.gvModel)
|
|
183
|
+
const caps = getDeviceCapabilities(context.gvModel, context.firmware)
|
|
184
184
|
const [r, g, b] = k2rgb(params.value)
|
|
185
185
|
|
|
186
186
|
let awsParams
|
|
@@ -224,6 +224,8 @@ function buildSceneCommand(params) {
|
|
|
224
224
|
if (params.value[0]) {
|
|
225
225
|
const splitCode = params.value[0].split(',')
|
|
226
226
|
result.awsParams = { cmd: 'ptReal', data: { command: splitCode } }
|
|
227
|
+
// The local api accepts `ptReal` even though it is not in the documented
|
|
228
|
+
// command set, which is what lets scenes work without the cloud
|
|
227
229
|
result.lanParams = { cmd: 'ptReal', data: { command: splitCode } }
|
|
228
230
|
}
|
|
229
231
|
if (params.value[1]) {
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import platformConsts from './constants.js'
|
|
2
|
+
import { parseDeviceId } from './functions.js'
|
|
3
|
+
import platformLang from './lang-en.js'
|
|
4
|
+
|
|
5
|
+
const WHITESPACE_REGEX = /\s+/g
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Reads the user's settings, warns about anything that will not be used, and
|
|
9
|
+
* fills in the defaults for the rest.
|
|
10
|
+
*
|
|
11
|
+
* Lifted out of the platform unchanged. It was the largest single thing in
|
|
12
|
+
* there and had nothing to do with talking to devices, so a change to how a
|
|
13
|
+
* device works no longer means opening the same file as a change to how a
|
|
14
|
+
* setting is read.
|
|
15
|
+
*
|
|
16
|
+
* It writes `config`, `deviceConf` and `ignoredDevices` onto the platform
|
|
17
|
+
* rather than returning them, which is how it always worked.
|
|
18
|
+
*/
|
|
19
|
+
export function applyUserConfig(platform, config) {
|
|
20
|
+
// These shorthand functions save line space during config parsing
|
|
21
|
+
const logDefault = (k, def) => {
|
|
22
|
+
platform.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgDef, def)
|
|
23
|
+
}
|
|
24
|
+
const logDuplicate = (k) => {
|
|
25
|
+
platform.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgDup)
|
|
26
|
+
}
|
|
27
|
+
const logIgnore = (k) => {
|
|
28
|
+
platform.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgIgn)
|
|
29
|
+
}
|
|
30
|
+
const logIgnoreItem = (k) => {
|
|
31
|
+
platform.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgIgnItem)
|
|
32
|
+
}
|
|
33
|
+
const logIncrease = (k, min) => {
|
|
34
|
+
platform.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgLow, min)
|
|
35
|
+
}
|
|
36
|
+
const logQuotes = (k) => {
|
|
37
|
+
platform.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgQts)
|
|
38
|
+
}
|
|
39
|
+
const logRemove = (k) => {
|
|
40
|
+
platform.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgRmv)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Begin applying the user's config
|
|
44
|
+
Object.entries(config).forEach((entry) => {
|
|
45
|
+
const [key, val] = entry
|
|
46
|
+
switch (key) {
|
|
47
|
+
case 'apiKey':
|
|
48
|
+
case 'code':
|
|
49
|
+
case 'password':
|
|
50
|
+
case 'username':
|
|
51
|
+
if (typeof val !== 'string' || val === '') {
|
|
52
|
+
logIgnore(key)
|
|
53
|
+
} else {
|
|
54
|
+
platform.config[key] = val
|
|
55
|
+
}
|
|
56
|
+
break
|
|
57
|
+
case 'lanScanSubnets': {
|
|
58
|
+
// Comma-separated list of CIDR subnets to scan directly (for devices
|
|
59
|
+
// on a different subnet from Homebridge, which multicast can't reach)
|
|
60
|
+
if (typeof val !== 'string' || val.trim() === '') {
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
const subnets = val
|
|
64
|
+
.split(',')
|
|
65
|
+
.map(s => s.trim())
|
|
66
|
+
.filter(s => /^\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}$/.test(s))
|
|
67
|
+
if (subnets.length === 0) {
|
|
68
|
+
logIgnore(key)
|
|
69
|
+
} else {
|
|
70
|
+
platform.config[key] = subnets
|
|
71
|
+
}
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
case 'awsDisable':
|
|
75
|
+
case 'bleDisable':
|
|
76
|
+
case 'colourSafeMode':
|
|
77
|
+
case 'disableDeviceLogging':
|
|
78
|
+
case 'ignoreMatter':
|
|
79
|
+
case 'lanDisable':
|
|
80
|
+
if (typeof val === 'string') {
|
|
81
|
+
logQuotes(key)
|
|
82
|
+
}
|
|
83
|
+
platform.config[key] = val === 'false' ? false : !!val
|
|
84
|
+
break
|
|
85
|
+
case 'bleControlInterval':
|
|
86
|
+
case 'bleRefreshTime':
|
|
87
|
+
case 'httpRefreshTime':
|
|
88
|
+
case 'lanRefreshTime':
|
|
89
|
+
case 'lanScanInterval': {
|
|
90
|
+
if (typeof val === 'string') {
|
|
91
|
+
logQuotes(key)
|
|
92
|
+
}
|
|
93
|
+
const intVal = Number.parseInt(val, 10)
|
|
94
|
+
if (Number.isNaN(intVal)) {
|
|
95
|
+
logDefault(key, platformConsts.defaultValues[key])
|
|
96
|
+
platform.config[key] = platformConsts.defaultValues[key]
|
|
97
|
+
} else if (intVal < platformConsts.minValues[key]) {
|
|
98
|
+
logIncrease(key, platformConsts.minValues[key])
|
|
99
|
+
platform.config[key] = platformConsts.minValues[key]
|
|
100
|
+
} else {
|
|
101
|
+
platform.config[key] = intVal
|
|
102
|
+
}
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
case 'dehumidifierDevices':
|
|
106
|
+
case 'fanDevices':
|
|
107
|
+
case 'heaterDevices':
|
|
108
|
+
case 'humidifierDevices':
|
|
109
|
+
case 'iceMakerDevices':
|
|
110
|
+
case 'kettleDevices':
|
|
111
|
+
case 'leakDevices':
|
|
112
|
+
case 'lightDevices':
|
|
113
|
+
case 'purifierDevices':
|
|
114
|
+
case 'diffuserDevices':
|
|
115
|
+
case 'switchDevices':
|
|
116
|
+
case 'thermoDevices':
|
|
117
|
+
if (Array.isArray(val) && val.length > 0) {
|
|
118
|
+
val.forEach((x) => {
|
|
119
|
+
if (!x.deviceId) {
|
|
120
|
+
logIgnoreItem(key)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const id = parseDeviceId(x.deviceId)
|
|
124
|
+
if (Object.keys(platform.deviceConf).includes(id)) {
|
|
125
|
+
logDuplicate(`${key}.${id}`)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
const entries = Object.entries(x)
|
|
129
|
+
if (entries.length === 1) {
|
|
130
|
+
logRemove(`${key}.${id}`)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
platform.deviceConf[id] = {}
|
|
134
|
+
entries.forEach((subEntry) => {
|
|
135
|
+
const [k, v] = subEntry
|
|
136
|
+
switch (k) {
|
|
137
|
+
case 'adaptiveLightingShift':
|
|
138
|
+
case 'brightnessStep':
|
|
139
|
+
case 'lowBattThreshold': {
|
|
140
|
+
if (typeof v === 'string') {
|
|
141
|
+
logQuotes(`${key}.${k}`)
|
|
142
|
+
}
|
|
143
|
+
const intVal = Number.parseInt(v, 10)
|
|
144
|
+
if (Number.isNaN(intVal)) {
|
|
145
|
+
logDefault(`${key}.${id}.${k}`, platformConsts.defaultValues[k])
|
|
146
|
+
platform.deviceConf[id][k] = platformConsts.defaultValues[k]
|
|
147
|
+
} else if (intVal < platformConsts.minValues[k]) {
|
|
148
|
+
logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k])
|
|
149
|
+
platform.deviceConf[id][k] = platformConsts.minValues[k]
|
|
150
|
+
} else {
|
|
151
|
+
platform.deviceConf[id][k] = intVal
|
|
152
|
+
}
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
case 'awsBrightnessNoScale':
|
|
156
|
+
case 'hideLight':
|
|
157
|
+
case 'hideModeGreenTea':
|
|
158
|
+
case 'hideModeOolongTea':
|
|
159
|
+
case 'hideModeCoffee':
|
|
160
|
+
case 'hideModeBlackTea':
|
|
161
|
+
case 'showCustomMode1':
|
|
162
|
+
case 'showCustomMode2':
|
|
163
|
+
case 'showExtraSwitch':
|
|
164
|
+
case 'tempReporting':
|
|
165
|
+
if (typeof v === 'string') {
|
|
166
|
+
logQuotes(`${key}.${id}.${k}`)
|
|
167
|
+
}
|
|
168
|
+
platform.deviceConf[id][k] = v === 'false' ? false : !!v
|
|
169
|
+
break
|
|
170
|
+
case 'awsColourMode':
|
|
171
|
+
case 'openApiTempUnit':
|
|
172
|
+
case 'showAs': {
|
|
173
|
+
if (typeof v !== 'string' || !platformConsts.allowed[k].includes(v)) {
|
|
174
|
+
logIgnore(`${key}.${id}.${k}`)
|
|
175
|
+
} else {
|
|
176
|
+
platform.deviceConf[id][k] = v
|
|
177
|
+
}
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
case 'customAddress':
|
|
181
|
+
case 'customIPAddress':
|
|
182
|
+
if (typeof v !== 'string' || v === '') {
|
|
183
|
+
logIgnore(`${key}.${id}.${k}`)
|
|
184
|
+
} else {
|
|
185
|
+
platform.deviceConf[id][k] = v.replace(WHITESPACE_REGEX, '')
|
|
186
|
+
}
|
|
187
|
+
break
|
|
188
|
+
case 'deviceId':
|
|
189
|
+
break
|
|
190
|
+
case 'diyMode':
|
|
191
|
+
case 'diyModeTwo':
|
|
192
|
+
case 'diyModeThree':
|
|
193
|
+
case 'diyModeFour':
|
|
194
|
+
case 'musicMode':
|
|
195
|
+
case 'musicModeTwo':
|
|
196
|
+
case 'scene':
|
|
197
|
+
case 'sceneTwo':
|
|
198
|
+
case 'sceneThree':
|
|
199
|
+
case 'sceneFour':
|
|
200
|
+
case 'segmented':
|
|
201
|
+
case 'segmentedTwo':
|
|
202
|
+
case 'segmentedThree':
|
|
203
|
+
case 'segmentedFour':
|
|
204
|
+
case 'temperatureSource':
|
|
205
|
+
case 'videoMode':
|
|
206
|
+
case 'videoModeTwo': {
|
|
207
|
+
if (typeof v === 'string') {
|
|
208
|
+
platform.log.warn(`${key}.${id}.${k} incorrectly configured - please use the config screen to reconfigure this item:`)
|
|
209
|
+
platform.log.warn(`${key}.${id}.${k}: ${v}`)
|
|
210
|
+
}
|
|
211
|
+
if (typeof v === 'object') {
|
|
212
|
+
// object - only allowed keys are 'sceneCode', 'bleCode' and 'showAs'
|
|
213
|
+
const subEntries = Object.entries(v)
|
|
214
|
+
if (subEntries.length > 0) {
|
|
215
|
+
platform.deviceConf[id][k] = {}
|
|
216
|
+
subEntries.forEach((subSubEntry) => {
|
|
217
|
+
const [k1, v1] = subSubEntry
|
|
218
|
+
switch (k1) {
|
|
219
|
+
case 'bleCode':
|
|
220
|
+
case 'sceneCode':
|
|
221
|
+
if (typeof v1 !== 'string' || v1 === '') {
|
|
222
|
+
logIgnore(`${key}.${id}.${k}.${k1}`)
|
|
223
|
+
} else {
|
|
224
|
+
platform.deviceConf[id][k][k1] = v1
|
|
225
|
+
}
|
|
226
|
+
break
|
|
227
|
+
case 'showAs': {
|
|
228
|
+
if (typeof v1 !== 'string' || !['default', 'switch'].includes(v1)) {
|
|
229
|
+
logIgnore(`${key}.${id}.${k}.${k1}`)
|
|
230
|
+
} else {
|
|
231
|
+
platform.deviceConf[id][k][k1] = v1
|
|
232
|
+
}
|
|
233
|
+
break
|
|
234
|
+
}
|
|
235
|
+
default:
|
|
236
|
+
logIgnore(`${key}.${id}.${k}.${k1}`)
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
} else {
|
|
241
|
+
logIgnore(`${key}.${id}.${k}`)
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
logIgnore(`${key}.${id}.${k}`)
|
|
245
|
+
}
|
|
246
|
+
break
|
|
247
|
+
}
|
|
248
|
+
case 'ignoreDevice':
|
|
249
|
+
if (typeof v === 'string') {
|
|
250
|
+
logQuotes(`${key}.${id}.${k}`)
|
|
251
|
+
}
|
|
252
|
+
if (!!v && v !== 'false') {
|
|
253
|
+
platform.ignoredDevices.push(id)
|
|
254
|
+
}
|
|
255
|
+
break
|
|
256
|
+
case 'label':
|
|
257
|
+
if (typeof v !== 'string' || v === '') {
|
|
258
|
+
logIgnore(`${key}.${id}.${k}`)
|
|
259
|
+
} else {
|
|
260
|
+
platform.deviceConf[id][k] = v
|
|
261
|
+
}
|
|
262
|
+
break
|
|
263
|
+
default:
|
|
264
|
+
logRemove(`${key}.${id}.${k}`)
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
} else {
|
|
269
|
+
logIgnore(key)
|
|
270
|
+
}
|
|
271
|
+
break
|
|
272
|
+
case 'name':
|
|
273
|
+
case 'platform':
|
|
274
|
+
break
|
|
275
|
+
default:
|
|
276
|
+
logRemove(key)
|
|
277
|
+
break
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
}
|