@homebridge-plugins/homebridge-meross 10.20.0 → 10.20.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/config.schema.json +4 -0
- package/lib/device/baby.js +7 -7
- package/lib/device/cooler-single.js +6 -7
- package/lib/device/diffuser.js +7 -7
- package/lib/device/fan.js +6 -6
- package/lib/device/garage-main.js +3 -4
- package/lib/device/garage-single.js +4 -5
- package/lib/device/garage-sub.js +3 -4
- package/lib/device/heater-single.js +6 -7
- package/lib/device/hub-main.js +3 -4
- package/lib/device/hub-sprinkler.js +3 -4
- package/lib/device/hub-valve.js +5 -6
- package/lib/device/humidifier.js +6 -6
- package/lib/device/index.js +2 -0
- package/lib/device/light-cct.js +5 -5
- package/lib/device/light-dimmer.js +4 -4
- package/lib/device/light-rgb.js +4 -4
- package/lib/device/outlet-multi.js +4 -5
- package/lib/device/outlet-single.js +6 -7
- package/lib/device/power-strip.js +4 -5
- package/lib/device/purifier-single.js +4 -5
- package/lib/device/purifier.js +6 -7
- package/lib/device/roller-location.js +4 -5
- package/lib/device/roller.js +4 -5
- package/lib/device/sensor-presence.js +3 -4
- package/lib/device/switch-multi.js +4 -5
- package/lib/device/switch-single.js +6 -7
- package/lib/device/template.js +3 -4
- package/lib/device/thermostat-mts215.js +6 -7
- package/lib/device/thermostat-mts300.js +7 -8
- package/lib/device/thermostat-mts960.js +213 -0
- package/lib/device/thermostat.js +6 -7
- package/lib/homebridge-ui/config-key.js +57 -0
- package/lib/homebridge-ui/public/index.html +33 -6
- package/lib/homebridge-ui/server.js +1 -45
- package/lib/platform.js +11 -0
- package/lib/utils/constants.js +4 -1
- package/lib/utils/functions.js +14 -0
- package/package.json +9 -9
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import PQueue from 'p-queue'
|
|
2
|
+
|
|
3
|
+
import mqttClient from '../connection/mqtt.js'
|
|
4
|
+
import platformConsts from '../utils/constants.js'
|
|
5
|
+
import { hasProperty, isTimeoutError, parseError } from '../utils/functions.js'
|
|
6
|
+
import platformLang from '../utils/lang-en.js'
|
|
7
|
+
|
|
8
|
+
// The MTS960 plug-in thermostat speaks Appliance.Control.Thermostat.ModeB, not
|
|
9
|
+
// the .Mode the MTS200 uses. Its digest arrives as `thermostat.modeB`, its
|
|
10
|
+
// temperatures are hundredths of a degree, and it uses 1 for on and 2 for off.
|
|
11
|
+
// Read with the MTS200 handler, none of that matched, so the temperature sat at
|
|
12
|
+
// HomeKit's default of 0 and the heat switch never moved (#809).
|
|
13
|
+
const ON = 1
|
|
14
|
+
const OFF = 2
|
|
15
|
+
const WORKING_HEAT = 1
|
|
16
|
+
const WORKING_COOL = 2
|
|
17
|
+
const MODE_HEAT_COOL = 1
|
|
18
|
+
|
|
19
|
+
export default class {
|
|
20
|
+
constructor(platform, accessory) {
|
|
21
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
22
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
23
|
+
this.hapServ = platform.api.hap.Service
|
|
24
|
+
this.platform = platform
|
|
25
|
+
this.accessory = accessory
|
|
26
|
+
this.name = accessory.displayName
|
|
27
|
+
const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
|
|
28
|
+
? platform.config.cloudRefreshRate
|
|
29
|
+
: platformConsts.defaultValues.cloudRefreshRate
|
|
30
|
+
const localRefreshRate = hasProperty(platform.config, 'refreshRate')
|
|
31
|
+
? platform.config.refreshRate
|
|
32
|
+
: platformConsts.defaultValues.refreshRate
|
|
33
|
+
this.pollInterval = accessory.context.connection === 'local'
|
|
34
|
+
? localRefreshRate
|
|
35
|
+
: cloudRefreshRate
|
|
36
|
+
this.tempScale = 100
|
|
37
|
+
|
|
38
|
+
this.service = this.accessory.getService(this.hapServ.Thermostat)
|
|
39
|
+
|| this.accessory.addService(this.hapServ.Thermostat)
|
|
40
|
+
|
|
41
|
+
// Off, heat and cool - the plug drives either a heater or a cooler
|
|
42
|
+
this.service
|
|
43
|
+
.getCharacteristic(this.hapChar.TargetHeatingCoolingState)
|
|
44
|
+
.setProps({ minValue: 0, maxValue: 2, validValues: [0, 1, 2] })
|
|
45
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
46
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.TargetHeatingCoolingState).value
|
|
47
|
+
|
|
48
|
+
this.service
|
|
49
|
+
.getCharacteristic(this.hapChar.TargetTemperature)
|
|
50
|
+
.setProps({ minValue: 5, maxValue: 35, minStep: 0.5 })
|
|
51
|
+
.onSet(async value => this.internalTargetUpdate(value))
|
|
52
|
+
this.cacheTarg = this.service.getCharacteristic(this.hapChar.TargetTemperature).value
|
|
53
|
+
this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
54
|
+
|
|
55
|
+
this.accessory.eveService = new platform.eveService('custom', this.accessory, { log: () => {} })
|
|
56
|
+
|
|
57
|
+
this.updateInProgress = false
|
|
58
|
+
this.queue = new PQueue({ concurrency: 1, interval: 250, intervalCap: 1, timeout: 10000, throwOnTimeout: true })
|
|
59
|
+
this.queue.addEventListener('idle', () => {
|
|
60
|
+
this.updateInProgress = false
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if (accessory.context.connection !== 'local') {
|
|
64
|
+
this.accessory.mqtt = new mqttClient(platform, this.accessory)
|
|
65
|
+
this.accessory.mqtt.connect()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setTimeout(() => this.requestUpdate(true), 2000)
|
|
69
|
+
this.accessory.refreshInterval = setInterval(() => this.requestUpdate(), this.pollInterval * 1000)
|
|
70
|
+
|
|
71
|
+
platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, JSON.stringify({
|
|
72
|
+
connection: this.accessory.context.connection,
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async sendModeB(fields) {
|
|
77
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
78
|
+
namespace: 'Appliance.Control.Thermostat.ModeB',
|
|
79
|
+
payload: { modeB: [{ channel: 0, ...fields }] },
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async internalStateUpdate(value) {
|
|
84
|
+
try {
|
|
85
|
+
await this.queue.add(async () => {
|
|
86
|
+
if (value === this.cacheState) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
this.updateInProgress = true
|
|
90
|
+
await this.sendModeB(value === 0
|
|
91
|
+
? { onoff: OFF }
|
|
92
|
+
: { onoff: ON, mode: MODE_HEAT_COOL, working: value === 2 ? WORKING_COOL : WORKING_HEAT })
|
|
93
|
+
this.cacheState = value
|
|
94
|
+
this.accessory.log(`${platformLang.curState} [${['off', 'heat', 'cool'][value]}]`)
|
|
95
|
+
})
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const eText = isTimeoutError(err) ? platformLang.timeout : parseError(err)
|
|
98
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
this.service.updateCharacteristic(this.hapChar.TargetHeatingCoolingState, this.cacheState)
|
|
101
|
+
}, 2000)
|
|
102
|
+
throw new this.hapErr(-70402)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async internalTargetUpdate(value) {
|
|
107
|
+
try {
|
|
108
|
+
await this.queue.add(async () => {
|
|
109
|
+
if (value === this.cacheTarg) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
this.updateInProgress = true
|
|
113
|
+
await this.sendModeB({
|
|
114
|
+
mode: MODE_HEAT_COOL,
|
|
115
|
+
working: this.cacheState === 2 ? WORKING_COOL : WORKING_HEAT,
|
|
116
|
+
targetTemp: Math.round(value * this.tempScale),
|
|
117
|
+
})
|
|
118
|
+
this.cacheTarg = value
|
|
119
|
+
this.accessory.log(`${platformLang.curTarg} [${value}°C]`)
|
|
120
|
+
})
|
|
121
|
+
} catch (err) {
|
|
122
|
+
const eText = isTimeoutError(err) ? platformLang.timeout : parseError(err)
|
|
123
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
this.service.updateCharacteristic(this.hapChar.TargetTemperature, this.cacheTarg)
|
|
126
|
+
}, 2000)
|
|
127
|
+
throw new this.hapErr(-70402)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async requestUpdate(firstRun = false) {
|
|
132
|
+
try {
|
|
133
|
+
if (this.updateInProgress) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
await this.queue.add(async () => {
|
|
137
|
+
this.updateInProgress = true
|
|
138
|
+
const res = await this.platform.sendUpdate(this.accessory, {
|
|
139
|
+
namespace: 'Appliance.System.All',
|
|
140
|
+
payload: {},
|
|
141
|
+
})
|
|
142
|
+
this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
|
|
143
|
+
const data = res.data.payload
|
|
144
|
+
if (data.all?.digest?.thermostat) {
|
|
145
|
+
this.applyUpdate(data.all.digest.thermostat)
|
|
146
|
+
}
|
|
147
|
+
if (firstRun && data.all?.system?.hardware) {
|
|
148
|
+
this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
|
|
149
|
+
this.accessory.context.hardware = data.all.system.hardware.version
|
|
150
|
+
this.accessory.context.firmware = data.all.system.firmware?.version
|
|
151
|
+
this.platform.updateAccessory(this.accessory)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
} catch (err) {
|
|
155
|
+
const eText = isTimeoutError(err) ? platformLang.timeout : parseError(err)
|
|
156
|
+
this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
receiveUpdate(params) {
|
|
161
|
+
try {
|
|
162
|
+
this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
|
|
163
|
+
if (params.payload) {
|
|
164
|
+
this.applyUpdate(params.payload)
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
applyUpdate(data) {
|
|
172
|
+
const modeData = data.modeB?.[0]
|
|
173
|
+
if (!modeData) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
if (hasProperty(modeData, 'onoff') || hasProperty(modeData, 'working')) {
|
|
177
|
+
const onoff = modeData.onoff ?? (this.cacheState === 0 ? OFF : ON)
|
|
178
|
+
const working = modeData.working ?? (this.cacheState === 2 ? WORKING_COOL : WORKING_HEAT)
|
|
179
|
+
const newState = onoff === ON ? (working === WORKING_COOL ? 2 : 1) : 0
|
|
180
|
+
if (this.cacheState !== newState) {
|
|
181
|
+
this.service.updateCharacteristic(this.hapChar.TargetHeatingCoolingState, newState)
|
|
182
|
+
this.cacheState = newState
|
|
183
|
+
this.accessory.log(`${platformLang.curState} [${['off', 'heat', 'cool'][newState]}]`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Schedule and timer modes report a target of 0, which is not a setpoint
|
|
187
|
+
if (modeData.targetTemp > 0) {
|
|
188
|
+
const newTarg = Math.min(35, Math.max(5, Math.round(modeData.targetTemp / 50) / 2))
|
|
189
|
+
if (this.cacheTarg !== newTarg) {
|
|
190
|
+
this.service.updateCharacteristic(this.hapChar.TargetTemperature, newTarg)
|
|
191
|
+
this.cacheTarg = newTarg
|
|
192
|
+
this.accessory.log(`${platformLang.curTarg} [${newTarg}°C]`)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (hasProperty(modeData, 'currentTemp')) {
|
|
196
|
+
const newTemp = modeData.currentTemp / this.tempScale
|
|
197
|
+
if (this.cacheTemp !== newTemp) {
|
|
198
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, newTemp)
|
|
199
|
+
this.cacheTemp = newTemp
|
|
200
|
+
this.accessory.eveService.addEntry({ temp: newTemp })
|
|
201
|
+
this.accessory.log(`${platformLang.curTemp} [${newTemp}°C]`)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// `state` is the relay: 1 when the plug is powering the heater or cooler
|
|
205
|
+
if (hasProperty(modeData, 'state')) {
|
|
206
|
+
const relayOn = modeData.state === ON && this.cacheState !== 0
|
|
207
|
+
this.service.updateCharacteristic(
|
|
208
|
+
this.hapChar.CurrentHeatingCoolingState,
|
|
209
|
+
relayOn ? this.cacheState : 0,
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
package/lib/device/thermostat.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import PQueue from 'p-queue'
|
|
2
|
-
import { TimeoutError } from 'p-timeout'
|
|
3
2
|
|
|
4
3
|
import mqttClient from '../connection/mqtt.js'
|
|
5
4
|
import platformConsts from '../utils/constants.js'
|
|
6
|
-
import { hasProperty, parseError } from '../utils/functions.js'
|
|
5
|
+
import { hasProperty, isTimeoutError, parseError } from '../utils/functions.js'
|
|
7
6
|
import platformLang from '../utils/lang-en.js'
|
|
8
7
|
|
|
9
8
|
export default class {
|
|
@@ -112,7 +111,7 @@ export default class {
|
|
|
112
111
|
timeout: 10000,
|
|
113
112
|
throwOnTimeout: true,
|
|
114
113
|
})
|
|
115
|
-
this.queue.
|
|
114
|
+
this.queue.addEventListener('idle', () => {
|
|
116
115
|
this.updateInProgress = false
|
|
117
116
|
})
|
|
118
117
|
|
|
@@ -171,7 +170,7 @@ export default class {
|
|
|
171
170
|
})
|
|
172
171
|
} catch (err) {
|
|
173
172
|
// Catch any errors whilst updating the device
|
|
174
|
-
const eText = err
|
|
173
|
+
const eText = isTimeoutError(err) ? platformLang.timeout : parseError(err)
|
|
175
174
|
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
176
175
|
setTimeout(() => {
|
|
177
176
|
this.service.updateCharacteristic(this.hapChar.TargetHeatingCoolingState, this.cacheState)
|
|
@@ -227,7 +226,7 @@ export default class {
|
|
|
227
226
|
})
|
|
228
227
|
} catch (err) {
|
|
229
228
|
// Catch any errors whilst updating the device
|
|
230
|
-
const eText = err
|
|
229
|
+
const eText = isTimeoutError(err) ? platformLang.timeout : parseError(err)
|
|
231
230
|
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
232
231
|
setTimeout(() => {
|
|
233
232
|
this.service.updateCharacteristic(this.mode2Char[newMode], false)
|
|
@@ -285,7 +284,7 @@ export default class {
|
|
|
285
284
|
})
|
|
286
285
|
} catch (err) {
|
|
287
286
|
// Catch any errors whilst updating the device
|
|
288
|
-
const eText = err
|
|
287
|
+
const eText = isTimeoutError(err) ? platformLang.timeout : parseError(err)
|
|
289
288
|
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
290
289
|
setTimeout(() => {
|
|
291
290
|
this.service.updateCharacteristic(this.hapChar.TargetTemperature, this.cacheTarg)
|
|
@@ -381,7 +380,7 @@ export default class {
|
|
|
381
380
|
}
|
|
382
381
|
})
|
|
383
382
|
} catch (err) {
|
|
384
|
-
const eText = err
|
|
383
|
+
const eText = isTimeoutError(err) ? platformLang.timeout : parseError(err)
|
|
385
384
|
this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
|
|
386
385
|
|
|
387
386
|
// Set the homebridge-ui status of the device to offline if local and error is timeout
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import platformConsts from '../utils/constants.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which config array the settings page should put a device in, by its model.
|
|
5
|
+
*
|
|
6
|
+
* A model missing here cannot be added or hidden from the My Devices tab -
|
|
7
|
+
* the page just reports it as unsupported, even though the plugin itself runs
|
|
8
|
+
* it fine (#816).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} model the device model, as the plugin stores it
|
|
11
|
+
* @returns {string|null} the config array, or null when the model is unknown
|
|
12
|
+
*/
|
|
13
|
+
export function getDeviceConfigKey(model) {
|
|
14
|
+
if (!model) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
const m = platformConsts.models
|
|
18
|
+
if (m.switchSingle.includes(model)) {
|
|
19
|
+
return 'singleDevices'
|
|
20
|
+
}
|
|
21
|
+
if (Object.hasOwn(m.switchMulti, model)) {
|
|
22
|
+
return 'multiDevices'
|
|
23
|
+
}
|
|
24
|
+
if (m.lightDimmer.includes(model) || m.lightRGB.includes(model) || m.lightCCT.includes(model)) {
|
|
25
|
+
return 'lightDevices'
|
|
26
|
+
}
|
|
27
|
+
if (m.fan.includes(model)) {
|
|
28
|
+
return 'fanDevices'
|
|
29
|
+
}
|
|
30
|
+
if (m.diffuser.includes(model)) {
|
|
31
|
+
return 'diffuserDevices'
|
|
32
|
+
}
|
|
33
|
+
if (m.purifier.includes(model)) {
|
|
34
|
+
return 'purifierDevices'
|
|
35
|
+
}
|
|
36
|
+
if (m.humidifier.includes(model)) {
|
|
37
|
+
return 'humidifierDevices'
|
|
38
|
+
}
|
|
39
|
+
if (m.thermostat.includes(model) || m.thermostatMts215.includes(model) || m.thermostatMts300.includes(model) || m.thermostatMts960.includes(model)) {
|
|
40
|
+
return 'thermostatDevices'
|
|
41
|
+
}
|
|
42
|
+
if (m.garage.includes(model)) {
|
|
43
|
+
return 'garageDevices'
|
|
44
|
+
}
|
|
45
|
+
if (m.roller.includes(model)) {
|
|
46
|
+
return 'rollerDevices'
|
|
47
|
+
}
|
|
48
|
+
if (m.baby.includes(model)) {
|
|
49
|
+
return 'babyDevices'
|
|
50
|
+
}
|
|
51
|
+
// A hub's config entry is where its sensors' settings live, including the
|
|
52
|
+
// list of sensors to hide (#816)
|
|
53
|
+
if (m.sensorPresence.includes(model) || m.hubMain.includes(model)) {
|
|
54
|
+
return 'sensorDevices'
|
|
55
|
+
}
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
@@ -270,6 +270,14 @@
|
|
|
270
270
|
// by hand in a different format still counts as the same device
|
|
271
271
|
const looseId = id => (id || '').toString().toLowerCase().replace(/[^a-z0-9]+/g, '')
|
|
272
272
|
|
|
273
|
+
// A hub's sensors share the hub's config entry, so hiding one sensor
|
|
274
|
+
// lists its id in the entry's ignoreSubdevices - ignoreDevice there would
|
|
275
|
+
// hide the hub and every sensor on it (#816)
|
|
276
|
+
const subIdsOf = entry => (entry?.ignoreSubdevices || '').split(',').map(id => id.trim()).filter(Boolean)
|
|
277
|
+
const isHiddenIn = (entry, acc) => acc.context.subSerialNumber
|
|
278
|
+
? subIdsOf(entry).includes(acc.context.subSerialNumber)
|
|
279
|
+
: !!entry?.ignoreDevice
|
|
280
|
+
|
|
273
281
|
const getConfigBlocks = async () => {
|
|
274
282
|
const blocks = await homebridge.getPluginConfig()
|
|
275
283
|
if (!blocks.length) {
|
|
@@ -332,7 +340,9 @@
|
|
|
332
340
|
if (!Array.isArray(config[arrayKey])) {
|
|
333
341
|
config[arrayKey] = []
|
|
334
342
|
}
|
|
335
|
-
|
|
343
|
+
// A hub sensor's entry is the hub's own, so name it after the hub
|
|
344
|
+
const name = acc.context.subSerialNumber ? `${acc.context.model} hub` : acc.displayName
|
|
345
|
+
config[arrayKey].push({ name, serialNumber: looseId(acc.context.serialNumber) })
|
|
336
346
|
return true
|
|
337
347
|
}
|
|
338
348
|
|
|
@@ -351,7 +361,7 @@
|
|
|
351
361
|
// re-check and never add a duplicate - just correct the button
|
|
352
362
|
if (found) {
|
|
353
363
|
setConfigButton(true)
|
|
354
|
-
setHideControl(
|
|
364
|
+
setHideControl(isHiddenIn(config[found.key][found.index], acc))
|
|
355
365
|
homebridge.toast.info(acc.displayName + ' is already in the config.', 'Not Added')
|
|
356
366
|
return
|
|
357
367
|
}
|
|
@@ -418,7 +428,13 @@
|
|
|
418
428
|
}
|
|
419
429
|
found = findInConfig(config, acc.context.serialNumber)
|
|
420
430
|
}
|
|
421
|
-
config[found.key][found.index]
|
|
431
|
+
const entry = config[found.key][found.index]
|
|
432
|
+
const subId = acc.context.subSerialNumber
|
|
433
|
+
if (subId) {
|
|
434
|
+
entry.ignoreSubdevices = [...new Set([...subIdsOf(entry), subId])].join(',')
|
|
435
|
+
} else {
|
|
436
|
+
entry.ignoreDevice = true
|
|
437
|
+
}
|
|
422
438
|
await homebridge.updatePluginConfig(blocks)
|
|
423
439
|
setConfigButton(true)
|
|
424
440
|
setHideControl(true)
|
|
@@ -427,7 +443,18 @@
|
|
|
427
443
|
if (found) {
|
|
428
444
|
// Drop the flag rather than storing ignoreDevice false, to keep
|
|
429
445
|
// the entry clean
|
|
430
|
-
|
|
446
|
+
const entry = config[found.key][found.index]
|
|
447
|
+
const subId = acc.context.subSerialNumber
|
|
448
|
+
if (subId) {
|
|
449
|
+
const rest = subIdsOf(entry).filter(id => id !== subId)
|
|
450
|
+
if (rest.length > 0) {
|
|
451
|
+
entry.ignoreSubdevices = rest.join(',')
|
|
452
|
+
} else {
|
|
453
|
+
delete entry.ignoreSubdevices
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
delete entry.ignoreDevice
|
|
457
|
+
}
|
|
431
458
|
await homebridge.updatePluginConfig(blocks)
|
|
432
459
|
}
|
|
433
460
|
setConfigButton(!!found)
|
|
@@ -475,7 +502,7 @@
|
|
|
475
502
|
const nowFound = findInConfig(config, acc.context.serialNumber)
|
|
476
503
|
setConfigButton(!!nowFound)
|
|
477
504
|
if (nowFound) {
|
|
478
|
-
setHideControl(
|
|
505
|
+
setHideControl(isHiddenIn(config[nowFound.key][nowFound.index], acc))
|
|
479
506
|
}
|
|
480
507
|
}
|
|
481
508
|
} catch (err) {
|
|
@@ -607,7 +634,7 @@
|
|
|
607
634
|
const configBlocks = await getConfigBlocks()
|
|
608
635
|
const configEntry = findInConfig(configBlocks[0], context.serialNumber)
|
|
609
636
|
setConfigButton(!!configEntry)
|
|
610
|
-
setHideControl(!!
|
|
637
|
+
setHideControl(!!configEntry && isHiddenIn(configBlocks[0][configEntry.key][configEntry.index], { context }))
|
|
611
638
|
document.getElementById('configActionList').style.display = 'block'
|
|
612
639
|
homebridge.hideSpinner()
|
|
613
640
|
}
|
|
@@ -1,50 +1,6 @@
|
|
|
1
1
|
import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
function getDeviceConfigKey(model) {
|
|
6
|
-
if (!model) {
|
|
7
|
-
return null
|
|
8
|
-
}
|
|
9
|
-
const m = platformConsts.models
|
|
10
|
-
if (m.switchSingle.includes(model)) {
|
|
11
|
-
return 'singleDevices'
|
|
12
|
-
}
|
|
13
|
-
if (Object.hasOwn(m.switchMulti, model)) {
|
|
14
|
-
return 'multiDevices'
|
|
15
|
-
}
|
|
16
|
-
if (m.lightDimmer.includes(model) || m.lightRGB.includes(model) || m.lightCCT.includes(model)) {
|
|
17
|
-
return 'lightDevices'
|
|
18
|
-
}
|
|
19
|
-
if (m.fan.includes(model)) {
|
|
20
|
-
return 'fanDevices'
|
|
21
|
-
}
|
|
22
|
-
if (m.diffuser.includes(model)) {
|
|
23
|
-
return 'diffuserDevices'
|
|
24
|
-
}
|
|
25
|
-
if (m.purifier.includes(model)) {
|
|
26
|
-
return 'purifierDevices'
|
|
27
|
-
}
|
|
28
|
-
if (m.humidifier.includes(model)) {
|
|
29
|
-
return 'humidifierDevices'
|
|
30
|
-
}
|
|
31
|
-
if (m.thermostat.includes(model) || m.thermostatMts215.includes(model) || m.thermostatMts300.includes(model)) {
|
|
32
|
-
return 'thermostatDevices'
|
|
33
|
-
}
|
|
34
|
-
if (m.garage.includes(model)) {
|
|
35
|
-
return 'garageDevices'
|
|
36
|
-
}
|
|
37
|
-
if (m.roller.includes(model)) {
|
|
38
|
-
return 'rollerDevices'
|
|
39
|
-
}
|
|
40
|
-
if (m.baby.includes(model)) {
|
|
41
|
-
return 'babyDevices'
|
|
42
|
-
}
|
|
43
|
-
if (m.sensorPresence.includes(model)) {
|
|
44
|
-
return 'sensorDevices'
|
|
45
|
-
}
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
3
|
+
import { getDeviceConfigKey } from './config-key.js'
|
|
48
4
|
|
|
49
5
|
class PluginUiServer extends HomebridgePluginUiServer {
|
|
50
6
|
constructor() {
|
package/lib/platform.js
CHANGED
|
@@ -863,6 +863,17 @@ export default class {
|
|
|
863
863
|
this.applyAccessoryLogging(accessory)
|
|
864
864
|
accessory.control = new deviceTypes.deviceThermostatMts300(this, accessory)
|
|
865
865
|
/** */
|
|
866
|
+
} else if (platformConsts.models.thermostatMts960.includes(device.model)) {
|
|
867
|
+
/**
|
|
868
|
+
********
|
|
869
|
+
THERMOSTATS (MTS960)
|
|
870
|
+
*********
|
|
871
|
+
*/
|
|
872
|
+
accessory = this.devicesInHB.get(hbUUID) || this.addAccessory(device)
|
|
873
|
+
accessory.context = { ...accessory.context, ...context }
|
|
874
|
+
this.applyAccessoryLogging(accessory)
|
|
875
|
+
accessory.control = new deviceTypes.deviceThermostatMts960(this, accessory)
|
|
876
|
+
/** */
|
|
866
877
|
} else if (platformConsts.models.thermostatMts215.includes(device.model)) {
|
|
867
878
|
/**
|
|
868
879
|
********
|
package/lib/utils/constants.js
CHANGED
|
@@ -240,6 +240,7 @@ export default {
|
|
|
240
240
|
'MSS710',
|
|
241
241
|
'MSS710R',
|
|
242
242
|
'MSS810',
|
|
243
|
+
'MSS815',
|
|
243
244
|
'P11',
|
|
244
245
|
'R10',
|
|
245
246
|
'R11',
|
|
@@ -316,8 +317,9 @@ export default {
|
|
|
316
317
|
garage: ['MSG100', 'MSG150', 'MSG200'],
|
|
317
318
|
roller: ['MRS100', 'MRS105', 'MRS200'],
|
|
318
319
|
baby: ['HP110A', 'HP110AHK'],
|
|
319
|
-
thermostat: ['MTS200', 'MTS200B', 'MTS205', 'MTS205B'
|
|
320
|
+
thermostat: ['MTS200', 'MTS200B', 'MTS205', 'MTS205B'],
|
|
320
321
|
thermostatMts300: ['MTS300'],
|
|
322
|
+
thermostatMts960: ['MTS960'],
|
|
321
323
|
thermostatMts215: ['MTS215', 'MTS215B'],
|
|
322
324
|
sensorPresence: ['MS600'],
|
|
323
325
|
hubMain: ['MSH300', 'MSH300HK', 'MSH400', 'MSH450'],
|
|
@@ -387,6 +389,7 @@ export default {
|
|
|
387
389
|
MSP844: ['5', '9'], // https://github.com/homebridge-plugins/homebridge-meross/issues/578, https://github.com/homebridge-plugins/homebridge-meross/issues/585
|
|
388
390
|
MSS115: ['9'], // https://github.com/homebridge-plugins/homebridge-meross/issues/549
|
|
389
391
|
MSS315: ['9'], // https://github.com/homebridge-plugins/homebridge-meross/issues/537
|
|
392
|
+
MSS815: ['9'], // https://github.com/homebridge-plugins/homebridge-meross/issues/813
|
|
390
393
|
},
|
|
391
394
|
|
|
392
395
|
noLocalControl: ['MSH300', 'MSH300HK', 'MSH400', 'MSH450'],
|
package/lib/utils/functions.js
CHANGED
|
@@ -13,6 +13,19 @@ function generateRandomString(length) {
|
|
|
13
13
|
|
|
14
14
|
const hasProperty = (obj, prop) => Object.hasOwn(obj, prop)
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Whether an error means a request ran out of time, whichever library threw it.
|
|
18
|
+
*
|
|
19
|
+
* The device queue and the connection both time requests out, but they report
|
|
20
|
+
* it differently: the queue throws a DOMException named TimeoutError, and the
|
|
21
|
+
* connection throws p-timeout's own TimeoutError class. Checking for the class
|
|
22
|
+
* alone would miss the queue's timeouts and log them as a raw error instead of
|
|
23
|
+
* the plain "timed out" message. Both carry the same name, so check that.
|
|
24
|
+
*/
|
|
25
|
+
function isTimeoutError(err) {
|
|
26
|
+
return err?.name === 'TimeoutError'
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
function parseError(err, hideStack = []) {
|
|
17
30
|
let toReturn = err.message
|
|
18
31
|
if (err?.stack?.length > 0 && !hideStack.includes(err.message)) {
|
|
@@ -34,6 +47,7 @@ export {
|
|
|
34
47
|
encodeParams,
|
|
35
48
|
generateRandomString,
|
|
36
49
|
hasProperty,
|
|
50
|
+
isTimeoutError,
|
|
37
51
|
parseError,
|
|
38
52
|
sleep,
|
|
39
53
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@homebridge-plugins/homebridge-meross",
|
|
3
3
|
"alias": "Meross",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "10.20.0",
|
|
5
|
+
"version": "10.20.2-beta.0",
|
|
6
6
|
"description": "Homebridge plugin to integrate Meross devices into HomeKit.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "bwp91",
|
|
@@ -61,16 +61,16 @@
|
|
|
61
61
|
"changelog:sync": "node .github/scripts/sync-changelog.mjs"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@homebridge/plugin-ui-utils": "^2.2.
|
|
65
|
-
"axios": "^1.
|
|
66
|
-
"mqtt": "^5.
|
|
64
|
+
"@homebridge/plugin-ui-utils": "^2.2.6",
|
|
65
|
+
"axios": "^1.20.0",
|
|
66
|
+
"mqtt": "^5.16.1",
|
|
67
67
|
"node-persist": "^4.0.4",
|
|
68
|
-
"p-queue": "^
|
|
69
|
-
"p-timeout": "^
|
|
68
|
+
"p-queue": "^10.0.0",
|
|
69
|
+
"p-timeout": "^8.0.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@antfu/eslint-config": "^9.
|
|
73
|
-
"@vitest/coverage-v8": "^
|
|
74
|
-
"vitest": "^
|
|
72
|
+
"@antfu/eslint-config": "^9.5.1",
|
|
73
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
74
|
+
"vitest": "^5.0.5"
|
|
75
75
|
}
|
|
76
76
|
}
|