@homebridge-plugins/homebridge-meross 10.13.1 → 10.14.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 +11 -0
- package/README.md +1 -1
- package/config.schema.json +16 -0
- package/lib/device/index.js +2 -0
- package/lib/device/thermostat-mts300.js +529 -0
- package/lib/platform.js +11 -0
- package/lib/utils/constants.js +3 -2
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-meross` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v10.14.0 (2026-04-09)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- feat: added new thermostat models
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- chore: remove official node 20 support
|
|
14
|
+
- chore: dependency updates
|
|
15
|
+
|
|
5
16
|
## v10.13.1 (2026-04-06)
|
|
6
17
|
|
|
7
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Homebridge plugin to integrate Meross devices into HomeKit
|
|
|
30
30
|
### Prerequisites
|
|
31
31
|
|
|
32
32
|
- To use this plugin, you will need to already have:
|
|
33
|
-
- [Node](https://nodejs.org): latest version of `
|
|
33
|
+
- [Node](https://nodejs.org): latest version of `v22` or `v24` - any other major version is not supported.
|
|
34
34
|
- [Homebridge](https://homebridge.io): `v1.6` - refer to link for more information and installation instructions.
|
|
35
35
|
|
|
36
36
|
### Setup
|
package/config.schema.json
CHANGED
|
@@ -1344,6 +1344,22 @@
|
|
|
1344
1344
|
"title": "MTS205",
|
|
1345
1345
|
"enum": ["MTS205"]
|
|
1346
1346
|
},
|
|
1347
|
+
{
|
|
1348
|
+
"title": "MTS205B",
|
|
1349
|
+
"enum": ["MTS205B"]
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
"title": "MTS215",
|
|
1353
|
+
"enum": ["MTS215"]
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
"title": "MTS215B",
|
|
1357
|
+
"enum": ["MTS215B"]
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
"title": "MTS300",
|
|
1361
|
+
"enum": ["MTS300"]
|
|
1362
|
+
},
|
|
1347
1363
|
{
|
|
1348
1364
|
"title": "MTS960",
|
|
1349
1365
|
"enum": ["MTS960"]
|
package/lib/device/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import deviceSensorPresence from './sensor-presence.js'
|
|
|
28
28
|
import deviceSwitchMulti from './switch-multi.js'
|
|
29
29
|
import deviceSwitchSingle from './switch-single.js'
|
|
30
30
|
import deviceTemplate from './template.js'
|
|
31
|
+
import deviceThermostatMts300 from './thermostat-mts300.js'
|
|
31
32
|
import deviceThermostat from './thermostat.js'
|
|
32
33
|
|
|
33
34
|
export default {
|
|
@@ -62,4 +63,5 @@ export default {
|
|
|
62
63
|
deviceSwitchSingle,
|
|
63
64
|
deviceTemplate,
|
|
64
65
|
deviceThermostat,
|
|
66
|
+
deviceThermostatMts300,
|
|
65
67
|
}
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import PQueue from 'p-queue'
|
|
2
|
+
import { TimeoutError } from 'p-timeout'
|
|
3
|
+
|
|
4
|
+
import mqttClient from '../connection/mqtt.js'
|
|
5
|
+
import platformConsts from '../utils/constants.js'
|
|
6
|
+
import { hasProperty, parseError } from '../utils/functions.js'
|
|
7
|
+
import platformLang from '../utils/lang-en.js'
|
|
8
|
+
|
|
9
|
+
export default class {
|
|
10
|
+
constructor(platform, accessory) {
|
|
11
|
+
// Set up variables from the platform
|
|
12
|
+
this.cusChar = platform.cusChar
|
|
13
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
14
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
15
|
+
this.hapServ = platform.api.hap.Service
|
|
16
|
+
this.platform = platform
|
|
17
|
+
|
|
18
|
+
// Set up variables from the accessory
|
|
19
|
+
this.accessory = accessory
|
|
20
|
+
this.name = accessory.displayName
|
|
21
|
+
const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
|
|
22
|
+
? platform.config.cloudRefreshRate
|
|
23
|
+
: platformConsts.defaultValues.cloudRefreshRate
|
|
24
|
+
const localRefreshRate = hasProperty(platform.config, 'refreshRate')
|
|
25
|
+
? platform.config.refreshRate
|
|
26
|
+
: platformConsts.defaultValues.refreshRate
|
|
27
|
+
this.pollInterval = accessory.context.connection === 'local'
|
|
28
|
+
? localRefreshRate
|
|
29
|
+
: cloudRefreshRate
|
|
30
|
+
|
|
31
|
+
// MTS300 temperature values are scaled by 100 (0.01°C resolution)
|
|
32
|
+
this.tempScale = 100
|
|
33
|
+
|
|
34
|
+
// MTS300 mode mapping: 0=off, 1=heat, 2=cool, 3=auto
|
|
35
|
+
this.mode2Label = {
|
|
36
|
+
0: 'off',
|
|
37
|
+
1: 'heat',
|
|
38
|
+
2: 'cool',
|
|
39
|
+
3: 'auto',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Fan speed mapping: device 0-3 to HomeKit 0/33/66/100
|
|
43
|
+
this.mr2hk = (speed) => {
|
|
44
|
+
if (speed === 0) {
|
|
45
|
+
return 0
|
|
46
|
+
}
|
|
47
|
+
if (speed === 1) {
|
|
48
|
+
return 33
|
|
49
|
+
}
|
|
50
|
+
if (speed === 2) {
|
|
51
|
+
return 66
|
|
52
|
+
}
|
|
53
|
+
return 100
|
|
54
|
+
}
|
|
55
|
+
this.hk2mr = (speed) => {
|
|
56
|
+
if (speed <= 16) {
|
|
57
|
+
return 0
|
|
58
|
+
}
|
|
59
|
+
if (speed <= 49) {
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
if (speed <= 82) {
|
|
63
|
+
return 2
|
|
64
|
+
}
|
|
65
|
+
return 3
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add the thermostat service if it doesn't already exist
|
|
69
|
+
this.service = this.accessory.getService(this.hapServ.Thermostat)
|
|
70
|
+
|| this.accessory.addService(this.hapServ.Thermostat)
|
|
71
|
+
|
|
72
|
+
// MTS300 supports off/heat/cool/auto (0, 1, 2, 3)
|
|
73
|
+
this.service
|
|
74
|
+
.getCharacteristic(this.hapChar.TargetHeatingCoolingState)
|
|
75
|
+
.setProps({
|
|
76
|
+
minValue: 0,
|
|
77
|
+
maxValue: 3,
|
|
78
|
+
validValues: [0, 1, 2, 3],
|
|
79
|
+
})
|
|
80
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
81
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.TargetHeatingCoolingState).value
|
|
82
|
+
|
|
83
|
+
this.service
|
|
84
|
+
.getCharacteristic(this.hapChar.TargetTemperature)
|
|
85
|
+
.setProps({
|
|
86
|
+
minValue: 5,
|
|
87
|
+
maxValue: 35,
|
|
88
|
+
minStep: 0.5,
|
|
89
|
+
})
|
|
90
|
+
.onSet(async value => this.internalTargetUpdate(value))
|
|
91
|
+
this.cacheTarg = this.service.getCharacteristic(this.hapChar.TargetTemperature).value
|
|
92
|
+
|
|
93
|
+
this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
94
|
+
this.updateCache()
|
|
95
|
+
|
|
96
|
+
// Add the humidity sensor service if it doesn't already exist
|
|
97
|
+
this.humiService = this.accessory.getService(this.hapServ.HumiditySensor)
|
|
98
|
+
|| this.accessory.addService(this.hapServ.HumiditySensor)
|
|
99
|
+
this.cacheHumi = this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value
|
|
100
|
+
|
|
101
|
+
// Add the fan service if it doesn't already exist
|
|
102
|
+
this.fanService = this.accessory.getService('Fan')
|
|
103
|
+
|| this.accessory.addService(this.hapServ.Fan, 'Fan', 'fan')
|
|
104
|
+
|
|
105
|
+
this.fanService
|
|
106
|
+
.getCharacteristic(this.hapChar.On)
|
|
107
|
+
.onSet(async value => this.internalFanStateUpdate(value))
|
|
108
|
+
this.cacheFanState = this.fanService.getCharacteristic(this.hapChar.On).value
|
|
109
|
+
|
|
110
|
+
this.fanService
|
|
111
|
+
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
112
|
+
.setProps({
|
|
113
|
+
minStep: 33,
|
|
114
|
+
validValues: [0, 33, 66, 100],
|
|
115
|
+
})
|
|
116
|
+
.onSet(async value => this.internalFanSpeedUpdate(value))
|
|
117
|
+
this.cacheFanSpeed = this.hk2mr(
|
|
118
|
+
this.fanService.getCharacteristic(this.hapChar.RotationSpeed).value,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
122
|
+
this.accessory.eveService = new platform.eveService('custom', this.accessory, { log: () => {} })
|
|
123
|
+
|
|
124
|
+
// Create the queue used for sending device requests
|
|
125
|
+
this.updateInProgress = false
|
|
126
|
+
this.queue = new PQueue({
|
|
127
|
+
concurrency: 1,
|
|
128
|
+
interval: 250,
|
|
129
|
+
intervalCap: 1,
|
|
130
|
+
timeout: 10000,
|
|
131
|
+
throwOnTimeout: true,
|
|
132
|
+
})
|
|
133
|
+
this.queue.on('idle', () => {
|
|
134
|
+
this.updateInProgress = false
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Set up the mqtt client for cloud devices to send and receive device updates
|
|
138
|
+
if (accessory.context.connection !== 'local') {
|
|
139
|
+
this.accessory.mqtt = new mqttClient(platform, this.accessory)
|
|
140
|
+
this.accessory.mqtt.connect()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Always request a device update on startup, then start the interval for polling
|
|
144
|
+
setTimeout(() => this.requestUpdate(true), 2000)
|
|
145
|
+
this.accessory.refreshInterval = setInterval(
|
|
146
|
+
() => this.requestUpdate(),
|
|
147
|
+
this.pollInterval * 1000,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// Output the customised options to the log
|
|
151
|
+
const opts = JSON.stringify({
|
|
152
|
+
connection: this.accessory.context.connection,
|
|
153
|
+
})
|
|
154
|
+
platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async internalStateUpdate(value) {
|
|
158
|
+
try {
|
|
159
|
+
await this.queue.add(async () => {
|
|
160
|
+
if (value === this.cacheState) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.updateInProgress = true
|
|
165
|
+
|
|
166
|
+
const namespace = 'Appliance.Control.Thermostat.ModeC'
|
|
167
|
+
const payload = {
|
|
168
|
+
modeC: [
|
|
169
|
+
{
|
|
170
|
+
channel: 0,
|
|
171
|
+
onoff: value ? 1 : 0,
|
|
172
|
+
mode: value,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
178
|
+
namespace,
|
|
179
|
+
payload,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
this.cacheState = value
|
|
183
|
+
this.accessory.log(`${platformLang.curState} [${this.mode2Label[value] || 'unknown'}]`)
|
|
184
|
+
})
|
|
185
|
+
} catch (err) {
|
|
186
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
187
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
this.service.updateCharacteristic(this.hapChar.TargetHeatingCoolingState, this.cacheState)
|
|
190
|
+
}, 2000)
|
|
191
|
+
throw new this.hapErr(-70402)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async internalTargetUpdate(value) {
|
|
196
|
+
try {
|
|
197
|
+
await this.queue.add(async () => {
|
|
198
|
+
if (value === this.cacheTarg) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.updateInProgress = true
|
|
203
|
+
|
|
204
|
+
// Send the appropriate setpoint based on current mode
|
|
205
|
+
const scaledValue = value * this.tempScale
|
|
206
|
+
const payload = { modeC: [{ channel: 0 }] }
|
|
207
|
+
if (this.cacheState === 2) {
|
|
208
|
+
payload.modeC[0].coolSetpoint = scaledValue
|
|
209
|
+
} else {
|
|
210
|
+
payload.modeC[0].heatSetpoint = scaledValue
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const namespace = 'Appliance.Control.Thermostat.ModeC'
|
|
214
|
+
|
|
215
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
216
|
+
namespace,
|
|
217
|
+
payload,
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
this.cacheTarg = value
|
|
221
|
+
this.accessory.log(`${platformLang.curTarg} [${value}°C]`)
|
|
222
|
+
|
|
223
|
+
// Update the current heating/cooling state
|
|
224
|
+
this.updateCurrentState()
|
|
225
|
+
})
|
|
226
|
+
} catch (err) {
|
|
227
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
228
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
this.service.updateCharacteristic(this.hapChar.TargetTemperature, this.cacheTarg)
|
|
231
|
+
}, 2000)
|
|
232
|
+
throw new this.hapErr(-70402)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async internalFanStateUpdate(value) {
|
|
237
|
+
try {
|
|
238
|
+
await this.queue.add(async () => {
|
|
239
|
+
if (value === this.cacheFanState) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.updateInProgress = true
|
|
244
|
+
|
|
245
|
+
const namespace = 'Appliance.Control.Thermostat.ModeC'
|
|
246
|
+
const payload = {
|
|
247
|
+
modeC: [
|
|
248
|
+
{
|
|
249
|
+
channel: 0,
|
|
250
|
+
fMode: value ? 1 : 0,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
256
|
+
namespace,
|
|
257
|
+
payload,
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
this.cacheFanState = value
|
|
261
|
+
this.accessory.log(`[fan] ${platformLang.curState} [${value ? 'on' : 'off'}]`)
|
|
262
|
+
})
|
|
263
|
+
} catch (err) {
|
|
264
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
265
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
this.fanService.updateCharacteristic(this.hapChar.On, this.cacheFanState)
|
|
268
|
+
}, 2000)
|
|
269
|
+
throw new this.hapErr(-70402)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async internalFanSpeedUpdate(value) {
|
|
274
|
+
try {
|
|
275
|
+
await this.queue.add(async () => {
|
|
276
|
+
const mrVal = this.hk2mr(value)
|
|
277
|
+
if (mrVal === this.cacheFanSpeed) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.updateInProgress = true
|
|
282
|
+
|
|
283
|
+
const namespace = 'Appliance.Control.Thermostat.ModeC'
|
|
284
|
+
const payload = {
|
|
285
|
+
modeC: [
|
|
286
|
+
{
|
|
287
|
+
channel: 0,
|
|
288
|
+
speed: mrVal,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await this.platform.sendUpdate(this.accessory, {
|
|
294
|
+
namespace,
|
|
295
|
+
payload,
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
this.cacheFanSpeed = mrVal
|
|
299
|
+
this.accessory.log(`[fan] ${platformLang.curSpeed} [${mrVal}]`)
|
|
300
|
+
})
|
|
301
|
+
} catch (err) {
|
|
302
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
303
|
+
this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
this.fanService.updateCharacteristic(
|
|
306
|
+
this.hapChar.RotationSpeed,
|
|
307
|
+
this.mr2hk(this.cacheFanSpeed),
|
|
308
|
+
)
|
|
309
|
+
}, 2000)
|
|
310
|
+
throw new this.hapErr(-70402)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
updateCurrentState() {
|
|
315
|
+
let currentState = 0
|
|
316
|
+
if (this.cacheState === 1 && this.cacheTarg > this.cacheTemp) {
|
|
317
|
+
currentState = 1 // heating
|
|
318
|
+
} else if (this.cacheState === 2 && this.cacheTarg < this.cacheTemp) {
|
|
319
|
+
currentState = 2 // cooling
|
|
320
|
+
} else if (this.cacheState === 3) {
|
|
321
|
+
if (this.cacheTarg > this.cacheTemp) {
|
|
322
|
+
currentState = 1 // heating
|
|
323
|
+
} else if (this.cacheTarg < this.cacheTemp) {
|
|
324
|
+
currentState = 2 // cooling
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
this.service.updateCharacteristic(this.hapChar.CurrentHeatingCoolingState, currentState)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async updateCache() {
|
|
331
|
+
if (!this.platform.storageClientData) {
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
await this.platform.storageData.setItem(
|
|
337
|
+
`${this.accessory.context.serialNumber}_temp`,
|
|
338
|
+
this.cacheTemp,
|
|
339
|
+
)
|
|
340
|
+
} catch (err) {
|
|
341
|
+
this.accessory.logWarn(`${platformLang.storageWriteErr} ${parseError(err)}`)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async requestUpdate(firstRun = false) {
|
|
346
|
+
try {
|
|
347
|
+
if (this.updateInProgress) {
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
await this.queue.add(async () => {
|
|
352
|
+
this.updateInProgress = true
|
|
353
|
+
|
|
354
|
+
const res = await this.platform.sendUpdate(this.accessory, {
|
|
355
|
+
namespace: 'Appliance.System.All',
|
|
356
|
+
payload: {},
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res.data)}`)
|
|
360
|
+
|
|
361
|
+
const data = res.data.payload
|
|
362
|
+
if (data.all) {
|
|
363
|
+
if (data.all.digest?.thermostat) {
|
|
364
|
+
this.applyUpdate(data.all.digest.thermostat)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let needsUpdate = false
|
|
368
|
+
|
|
369
|
+
if (data.all.system) {
|
|
370
|
+
if (firstRun && data.all.system.hardware) {
|
|
371
|
+
this.accessory.context.macAddress = data.all.system.hardware.macAddress.toUpperCase()
|
|
372
|
+
this.accessory.context.hardware = data.all.system.hardware.version
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (data.all.system.firmware) {
|
|
376
|
+
if (this.accessory.context.ipAddress !== data.all.system.firmware.innerIp) {
|
|
377
|
+
this.accessory.context.ipAddress = data.all.system.firmware.innerIp
|
|
378
|
+
needsUpdate = true
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (firstRun) {
|
|
382
|
+
this.accessory.context.firmware = data.all.system.firmware.version
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (data.all.system.online) {
|
|
388
|
+
const isOnline = data.all.system.online.status === 1
|
|
389
|
+
if (this.accessory.context.isOnline !== isOnline) {
|
|
390
|
+
this.accessory.context.isOnline = isOnline
|
|
391
|
+
needsUpdate = true
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (needsUpdate || firstRun) {
|
|
396
|
+
this.platform.updateAccessory(this.accessory)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
} catch (err) {
|
|
401
|
+
const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
|
|
402
|
+
this.accessory.logDebugWarn(`${platformLang.reqFailed}: ${eText}`)
|
|
403
|
+
|
|
404
|
+
if (
|
|
405
|
+
(this.accessory.context.isOnline || firstRun)
|
|
406
|
+
&& ['EHOSTUNREACH', 'timed out'].some(el => eText.includes(el))
|
|
407
|
+
) {
|
|
408
|
+
this.accessory.context.isOnline = false
|
|
409
|
+
this.platform.updateAccessory(this.accessory)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
receiveUpdate(params) {
|
|
415
|
+
try {
|
|
416
|
+
this.accessory.logDebug(`${platformLang.incMQTT}: ${JSON.stringify(params)}`)
|
|
417
|
+
if (params.payload) {
|
|
418
|
+
this.applyUpdate(params.payload)
|
|
419
|
+
}
|
|
420
|
+
} catch (err) {
|
|
421
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
applyUpdate(data) {
|
|
426
|
+
try {
|
|
427
|
+
// MTS300 uses modeC instead of mode
|
|
428
|
+
const modeData = data.modeC?.[0]
|
|
429
|
+
if (modeData) {
|
|
430
|
+
let needsUpdate = false
|
|
431
|
+
|
|
432
|
+
// Update on/off and mode state
|
|
433
|
+
if (hasProperty(modeData, 'onoff') || hasProperty(modeData, 'mode')) {
|
|
434
|
+
const newOnoff = hasProperty(modeData, 'onoff') ? modeData.onoff : undefined
|
|
435
|
+
const newMode = hasProperty(modeData, 'mode') ? modeData.mode : undefined
|
|
436
|
+
|
|
437
|
+
// Determine the target state: off if onoff=0, otherwise use mode
|
|
438
|
+
let newState
|
|
439
|
+
if (newOnoff === 0) {
|
|
440
|
+
newState = 0
|
|
441
|
+
} else if (newMode !== undefined) {
|
|
442
|
+
newState = newMode
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (newState !== undefined && this.cacheState !== newState) {
|
|
446
|
+
this.service.updateCharacteristic(this.hapChar.TargetHeatingCoolingState, newState)
|
|
447
|
+
this.cacheState = newState
|
|
448
|
+
this.accessory.log(`${platformLang.curState} [${this.mode2Label[newState] || 'unknown'}]`)
|
|
449
|
+
needsUpdate = true
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Update target temperature from heat or cool setpoint
|
|
454
|
+
if (hasProperty(modeData, 'heatSetpoint') || hasProperty(modeData, 'coolSetpoint')) {
|
|
455
|
+
let newTarg
|
|
456
|
+
if (this.cacheState === 2 && hasProperty(modeData, 'coolSetpoint')) {
|
|
457
|
+
newTarg = modeData.coolSetpoint / this.tempScale
|
|
458
|
+
} else if (hasProperty(modeData, 'heatSetpoint')) {
|
|
459
|
+
newTarg = modeData.heatSetpoint / this.tempScale
|
|
460
|
+
} else if (hasProperty(modeData, 'coolSetpoint')) {
|
|
461
|
+
newTarg = modeData.coolSetpoint / this.tempScale
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (newTarg !== undefined && this.cacheTarg !== newTarg) {
|
|
465
|
+
this.service.updateCharacteristic(this.hapChar.TargetTemperature, newTarg)
|
|
466
|
+
this.cacheTarg = newTarg
|
|
467
|
+
this.accessory.log(`${platformLang.curTarg} [${newTarg}°C]`)
|
|
468
|
+
needsUpdate = true
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Update current temperature
|
|
473
|
+
const tempField = hasProperty(modeData, 'currentTemp')
|
|
474
|
+
? 'currentTemp'
|
|
475
|
+
: hasProperty(modeData, 'sensorTemp')
|
|
476
|
+
? 'sensorTemp'
|
|
477
|
+
: null
|
|
478
|
+
if (tempField) {
|
|
479
|
+
const newTemp = modeData[tempField] / this.tempScale
|
|
480
|
+
|
|
481
|
+
if (this.cacheTemp !== newTemp) {
|
|
482
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, newTemp)
|
|
483
|
+
this.cacheTemp = newTemp
|
|
484
|
+
this.accessory.eveService.addEntry({ temp: newTemp })
|
|
485
|
+
this.accessory.log(`${platformLang.curTemp} [${newTemp}°C]`)
|
|
486
|
+
needsUpdate = true
|
|
487
|
+
this.updateCache()
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Update fan state and speed
|
|
492
|
+
if (hasProperty(modeData, 'fMode')) {
|
|
493
|
+
const newFanState = modeData.fMode !== 0
|
|
494
|
+
if (this.cacheFanState !== newFanState) {
|
|
495
|
+
this.fanService.updateCharacteristic(this.hapChar.On, newFanState)
|
|
496
|
+
this.cacheFanState = newFanState
|
|
497
|
+
this.accessory.log(`[fan] ${platformLang.curState} [${newFanState ? 'on' : 'off'}]`)
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (hasProperty(modeData, 'speed')) {
|
|
501
|
+
const newSpeed = modeData.speed
|
|
502
|
+
if (this.cacheFanSpeed !== newSpeed) {
|
|
503
|
+
this.cacheFanSpeed = newSpeed
|
|
504
|
+
this.fanService.updateCharacteristic(this.hapChar.RotationSpeed, this.mr2hk(newSpeed))
|
|
505
|
+
this.accessory.log(`[fan] ${platformLang.curSpeed} [${newSpeed}]`)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (needsUpdate) {
|
|
510
|
+
this.updateCurrentState()
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Humidity data may come in a "more" object or sensor data
|
|
515
|
+
const moreData = data.more || data.modeC?.[0]
|
|
516
|
+
if (moreData && hasProperty(moreData, 'humi')) {
|
|
517
|
+
const newHumi = moreData.humi / 10
|
|
518
|
+
|
|
519
|
+
if (this.cacheHumi !== newHumi) {
|
|
520
|
+
this.humiService.updateCharacteristic(this.hapChar.CurrentRelativeHumidity, newHumi)
|
|
521
|
+
this.cacheHumi = newHumi
|
|
522
|
+
this.accessory.log(`${platformLang.curHumi} [${newHumi}%]`)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
package/lib/platform.js
CHANGED
|
@@ -835,6 +835,17 @@ export default class {
|
|
|
835
835
|
// Set up the main accessory for the baby monitor
|
|
836
836
|
accessory.control = new deviceTypes.deviceBaby(this, accessory, accessoryLight)
|
|
837
837
|
/** */
|
|
838
|
+
} else if (platformConsts.models.thermostatMts300.includes(device.model)) {
|
|
839
|
+
/**
|
|
840
|
+
********
|
|
841
|
+
THERMOSTATS (MTS300)
|
|
842
|
+
*********
|
|
843
|
+
*/
|
|
844
|
+
accessory = this.devicesInHB.get(hbUUID) || this.addAccessory(device)
|
|
845
|
+
accessory.context = { ...accessory.context, ...context }
|
|
846
|
+
this.applyAccessoryLogging(accessory)
|
|
847
|
+
accessory.control = new deviceTypes.deviceThermostatMts300(this, accessory)
|
|
848
|
+
/** */
|
|
838
849
|
} else if (platformConsts.models.thermostat.includes(device.model)) {
|
|
839
850
|
/**
|
|
840
851
|
********
|
package/lib/utils/constants.js
CHANGED
|
@@ -306,10 +306,11 @@ export default {
|
|
|
306
306
|
garage: ['MSG100', 'MSG150', 'MSG200'],
|
|
307
307
|
roller: ['MRS100', 'MRS105'],
|
|
308
308
|
baby: ['HP110A', 'HP110AHK'],
|
|
309
|
-
thermostat: ['MTS200', 'MTS200B', 'MTS205', 'MTS960'],
|
|
309
|
+
thermostat: ['MTS200', 'MTS200B', 'MTS205', 'MTS205B', 'MTS215', 'MTS215B', 'MTS960'],
|
|
310
|
+
thermostatMts300: ['MTS300'],
|
|
310
311
|
sensorPresence: ['MS600'],
|
|
311
312
|
hubMain: ['MSH300', 'MSH300HK', 'MSH400', 'MSH450'],
|
|
312
|
-
hubSub: ['GS559A', 'MS100', 'MS100F', 'MS130', 'MS200', 'MS400', 'MST100', 'MTS100', 'MTS100V3', 'MTS150', 'MTS150P'],
|
|
313
|
+
hubSub: ['GS559A', 'MS100', 'MS100F', 'MS130', 'MS130H', 'MS200', 'MS400', 'MST100', 'MTS100', 'MTS100V3', 'MTS150', 'MTS150P'],
|
|
313
314
|
template: [],
|
|
314
315
|
},
|
|
315
316
|
|
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.
|
|
5
|
+
"version": "10.14.0",
|
|
6
6
|
"description": "Homebridge plugin to integrate Meross devices into HomeKit.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "bwp91",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"main": "lib/index.js",
|
|
50
50
|
"engines": {
|
|
51
51
|
"homebridge": "^1.6.0 || ^2.0.0-beta.0",
|
|
52
|
-
"node": "^
|
|
52
|
+
"node": "^22 || ^24"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"lint": "eslint . --max-warnings=0",
|
|
@@ -57,13 +57,13 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@homebridge/plugin-ui-utils": "^2.2.3",
|
|
60
|
-
"axios": "^1.
|
|
60
|
+
"axios": "^1.15.0",
|
|
61
61
|
"mqtt": "^5.15.1",
|
|
62
62
|
"node-persist": "^4.0.4",
|
|
63
|
-
"p-queue": "^9.1.
|
|
63
|
+
"p-queue": "^9.1.2",
|
|
64
64
|
"p-timeout": "^7.0.1"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@antfu/eslint-config": "^8.
|
|
67
|
+
"@antfu/eslint-config": "^8.1.1"
|
|
68
68
|
}
|
|
69
69
|
}
|