@homebridge-plugins/homebridge-meross 10.20.1 → 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 +8 -0
- package/config.schema.json +4 -0
- package/lib/device/index.js +2 -0
- package/lib/device/thermostat-mts960.js +213 -0
- 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/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-meross` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v10.20.2 (Pending Release)
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- fix(ui): let a hub's sensors be hidden one by one from the my devices tab (#816)
|
|
10
|
+
- feat: add support for the mss815 in-wall switch (#813)
|
|
11
|
+
- fix(thermostat): give the mts960 its own handler, so it shows its temperature and mode (#809)
|
|
12
|
+
|
|
5
13
|
## v10.20.1 (2026-10-18)
|
|
6
14
|
|
|
7
15
|
### Changed
|
package/config.schema.json
CHANGED
package/lib/device/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import deviceSwitchSingle from './switch-single.js'
|
|
|
30
30
|
import deviceTemplate from './template.js'
|
|
31
31
|
import deviceThermostatMts215 from './thermostat-mts215.js'
|
|
32
32
|
import deviceThermostatMts300 from './thermostat-mts300.js'
|
|
33
|
+
import deviceThermostatMts960 from './thermostat-mts960.js'
|
|
33
34
|
import deviceThermostat from './thermostat.js'
|
|
34
35
|
|
|
35
36
|
export default {
|
|
@@ -65,5 +66,6 @@ export default {
|
|
|
65
66
|
deviceTemplate,
|
|
66
67
|
deviceThermostat,
|
|
67
68
|
deviceThermostatMts300,
|
|
69
|
+
deviceThermostatMts960,
|
|
68
70
|
deviceThermostatMts215,
|
|
69
71
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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/package.json
CHANGED