@homebridge-plugins/homebridge-meross 10.10.3 → 10.12.1-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 CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-meross` will be documented in this file.
4
4
 
5
+ ## v10.12.0 (2026-02-28)
6
+
7
+ ### Added
8
+
9
+ - support new models
10
+ - `MSS100` `MOP320` `MTS100` `MTS150P` `MST100` `MTS205` `P11` `R10` `R11` `R21` (beta)
11
+
12
+ ### Changed
13
+
14
+ - dependency updates + maintenance
15
+
16
+ ## v10.11.0 (2026-02-15)
17
+
18
+ ### Added
19
+
20
+ - add garage model `MSG150` (beta)
21
+ - add hub model `MSH450` (beta)
22
+
23
+ ### Changes
24
+
25
+ - determine debug mode from `-D` flag
26
+ - updated dependencies
27
+ - updated dependencies + lint rules
28
+ - update workflow action versions
29
+ - fix deprecate past releases script
30
+
5
31
  ## v10.10.3 (2025-12-05)
6
32
 
7
33
  ### Changes
package/CLAUDE.md ADDED
@@ -0,0 +1,74 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ Homebridge plugin that integrates Meross smart home devices into Apple HomeKit. Supports 31 device types with cloud (MQTT), local (HTTP), and hybrid connection modes. Written in JavaScript (ES modules), no TypeScript compilation step.
8
+
9
+ ## Commands
10
+
11
+ - **Lint**: `npm run lint` (eslint with zero warnings allowed)
12
+ - **Lint + fix**: `npm run lint:fix`
13
+ - **Install**: `npm install`
14
+
15
+ There are no automated tests. CI runs linting only (Node 20, 22, 24).
16
+
17
+ ## Code Style
18
+
19
+ Uses `@antfu/eslint-config` with these key rules:
20
+ - Single quotes, 1tbs brace style
21
+ - Sorted imports/exports via `perfectionist` plugin (grouped: types, then builtins, externals, internals)
22
+ - `no-undef` enabled
23
+ - Zero warnings tolerance (`--max-warnings=0`)
24
+
25
+ ## Architecture
26
+
27
+ ### Entry Point
28
+ `lib/index.js` registers the platform class with Homebridge under the alias "Meross".
29
+
30
+ ### Platform (`lib/platform.js`)
31
+ Central orchestrator (~1250 lines). Key responsibilities:
32
+ - **`constructor`**: Validates config, sets up Homebridge event listeners
33
+ - **`applyUserConfig`**: Parses and validates per-device config arrays (singleDevices, multiDevices, lightDevices, etc.)
34
+ - **`pluginSetup`**: Cloud login, device list retrieval, initializes all devices
35
+ - **`pluginShutdown`**: Closes MQTT connections, clears intervals
36
+ - **`initialiseDevice`**: Factory that maps device models to device classes using `platformConsts.models.*` lookups
37
+ - **`sendUpdate`**: Hybrid control - tries local HTTP first, falls back to cloud MQTT
38
+
39
+ ### Device Classes (`lib/device/`)
40
+ Each file exports a single class for one device type. Common pattern:
41
+ - Constructor sets up HAP services/characteristics and event handlers
42
+ - Uses `PQueue` (concurrency: 1, interval: 250ms) for sequential request queuing
43
+ - `internalStateUpdate(value)` - handles HomeKit commands → device
44
+ - `requestUpdate(firstRun)` - polls device state → HomeKit
45
+ - `externalUpdate(params)` - handles MQTT push updates → HomeKit
46
+ - State caching to prevent redundant updates
47
+
48
+ Multi-channel devices (garage, outlets, switches) create sub-accessories per channel. The garage door (MSG200) uses a 3-accessory pattern: hidden main + visible sub-doors.
49
+
50
+ ### Connection Layer (`lib/connection/`)
51
+ - **`http.js`**: Cloud REST API client. MD5-based auth signatures, spoofs iOS Meross app headers.
52
+ - **`mqtt.js`**: Cloud real-time client. Subscribes to `/app/{userId}/subscribe`, publishes commands, waits for response by messageId matching.
53
+
54
+ ### Utilities (`lib/utils/`)
55
+ - **`constants.js`**: Default config values, min values, device model → type mappings (this is where new device models are registered)
56
+ - **`functions.js`**: Small helpers (encodeParams, generateRandomString, hasProperty, parseError, sleep)
57
+ - **`colour.js`**: RGB↔HSV conversion, color temperature handling
58
+ - **`custom-chars.js`** / **`eve-chars.js`**: Custom HomeKit characteristics
59
+ - **`lang-en.js`**: All user-facing log strings
60
+
61
+ ### Fakegato (`lib/fakegato/`)
62
+ Eve app history service integration for home analytics.
63
+
64
+ ## Adding a New Device Type
65
+
66
+ 1. Add the model string to the appropriate array in `lib/utils/constants.js` under `models`
67
+ 2. Create a new device class in `lib/device/` following the existing pattern (see `switch-single.js` for a simple example)
68
+ 3. Export it from `lib/device/index.js`
69
+ 4. Add the initialization branch in `platform.js` `initialiseDevice()` method
70
+ 5. If the device needs per-device config, add allowed fields in `constants.js` `allowed` object and update `config.schema.json`
71
+
72
+ ## Configuration
73
+
74
+ `config.schema.json` (~90KB) defines the Homebridge UI config form. Global settings include connection mode, cloud credentials, and refresh rates. Per-device arrays allow overriding connection type, IP address (for local control), model code, and device-specific options.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
  <span align="center">
5
5
 
6
- # homebridge-meross
6
+ ## homebridge-meross
7
7
 
8
8
  Homebridge plugin to integrate Meross devices into HomeKit
9
9
 
@@ -181,6 +181,10 @@
181
181
  "description": "The model of this device.",
182
182
  "type": "string",
183
183
  "oneOf": [
184
+ {
185
+ "title": "MSS100",
186
+ "enum": ["MSS100"]
187
+ },
184
188
  {
185
189
  "title": "MSS105",
186
190
  "enum": ["MSS105"]
@@ -316,6 +320,18 @@
316
320
  {
317
321
  "title": "MSS810",
318
322
  "enum": ["MSS810"]
323
+ },
324
+ {
325
+ "title": "P11",
326
+ "enum": ["P11"]
327
+ },
328
+ {
329
+ "title": "R10",
330
+ "enum": ["R10"]
331
+ },
332
+ {
333
+ "title": "R11",
334
+ "enum": ["R11"]
319
335
  }
320
336
  ],
321
337
  "condition": {
@@ -550,6 +566,14 @@
550
566
  {
551
567
  "title": "MSS630",
552
568
  "enum": ["MSS630"]
569
+ },
570
+ {
571
+ "title": "MOP320",
572
+ "enum": ["MOP320"]
573
+ },
574
+ {
575
+ "title": "R21",
576
+ "enum": ["R21"]
553
577
  }
554
578
  ],
555
579
  "condition": {
@@ -1012,6 +1036,10 @@
1012
1036
  {
1013
1037
  "title": "MOD100",
1014
1038
  "enum": ["MOD100"]
1039
+ },
1040
+ {
1041
+ "title": "MOD150",
1042
+ "enum": ["MOD150"]
1015
1043
  }
1016
1044
  ],
1017
1045
  "condition": {
@@ -1312,6 +1340,10 @@
1312
1340
  "title": "MTS200B",
1313
1341
  "enum": ["MTS200B"]
1314
1342
  },
1343
+ {
1344
+ "title": "MTS205",
1345
+ "enum": ["MTS205"]
1346
+ },
1315
1347
  {
1316
1348
  "title": "MTS960",
1317
1349
  "enum": ["MTS960"]
@@ -1411,6 +1443,10 @@
1411
1443
  "title": "MSG100",
1412
1444
  "enum": ["MSG100"]
1413
1445
  },
1446
+ {
1447
+ "title": "MSG150",
1448
+ "enum": ["MSG150"]
1449
+ },
1414
1450
  {
1415
1451
  "title": "MSG200",
1416
1452
  "enum": ["MSG200"]
@@ -1730,6 +1766,10 @@
1730
1766
  "title": "MSH400",
1731
1767
  "enum": ["MSH400"]
1732
1768
  },
1769
+ {
1770
+ "title": "MSH450",
1771
+ "enum": ["MSH450"]
1772
+ },
1733
1773
  {
1734
1774
  "title": "MS600",
1735
1775
  "enum": ["MS600"]
package/eslint.config.js CHANGED
@@ -8,29 +8,24 @@ export default antfu(
8
8
  rules: {
9
9
  'curly': ['error', 'multi-line'],
10
10
  'new-cap': 'off',
11
- 'jsdoc/check-alignment': 'warn',
12
- 'jsdoc/check-line-alignment': 'warn',
13
- 'jsdoc/require-returns-check': 0,
14
- 'jsdoc/require-returns-description': 0,
15
11
  'no-undef': 'error',
16
12
  'perfectionist/sort-exports': 'error',
17
13
  'perfectionist/sort-imports': [
18
14
  'error',
19
15
  {
20
16
  groups: [
21
- 'type',
22
- 'internal-type',
17
+ ['type-builtin', 'type-external', 'type-internal'],
18
+ ['type-parent', 'type-sibling', 'type-index'],
23
19
  'builtin',
24
20
  'external',
25
21
  'internal',
26
- ['parent-type', 'sibling-type', 'index-type'],
27
22
  ['parent', 'sibling', 'index'],
28
- 'object',
23
+ 'side-effect',
29
24
  'unknown',
30
25
  ],
31
26
  order: 'asc',
32
27
  type: 'natural',
33
- newlinesBetween: 'always',
28
+ newlinesBetween: 1,
34
29
  },
35
30
  ],
36
31
  'perfectionist/sort-named-exports': 'error',
@@ -41,8 +36,17 @@ export default antfu(
41
36
  'style/quote-props': ['error', 'consistent-as-needed'],
42
37
  'test/no-only-tests': 'error',
43
38
  'unicorn/no-useless-spread': 'error',
44
- 'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }],
39
+ 'unused-imports/no-unused-vars': 0,
40
+ },
41
+ typescript: true,
42
+ },
43
+ {
44
+ files: ['**/*.md'],
45
+ rules: {
46
+ 'perfectionist/sort-exports': 'off',
47
+ 'perfectionist/sort-imports': 'off',
48
+ 'perfectionist/sort-named-exports': 'off',
49
+ 'perfectionist/sort-named-imports': 'off',
45
50
  },
46
- typescript: false,
47
51
  },
48
52
  )
@@ -18,6 +18,7 @@ export default class {
18
18
  // Set up variables from the accessory
19
19
  this.accessory = accessory
20
20
  this.mtsList = []
21
+ this.waterList = []
21
22
  this.name = accessory.displayName
22
23
  const cloudRefreshRate = hasProperty(platform.config, 'cloudRefreshRate')
23
24
  ? platform.config.cloudRefreshRate
@@ -118,6 +119,16 @@ export default class {
118
119
  update.voltage = subdevice.ms100.voltage
119
120
  }
120
121
  subAcc.control.applyUpdate(update)
122
+ } else if (subdevice.mst) {
123
+ // MST100 sprinkler timer - extract onoff from digest
124
+ const update = {}
125
+ if (hasProperty(subdevice.mst, 'onoff')) {
126
+ update.onoff = subdevice.mst.onoff
127
+ }
128
+ if (hasProperty(subdevice.mst, 'voltage')) {
129
+ update.voltage = subdevice.mst.voltage
130
+ }
131
+ subAcc.control.applyUpdate(update)
121
132
  } else if (subdevice.waterLeak) {
122
133
  // Apply the update to the accessory
123
134
  subAcc.control.applyUpdate(subdevice)
@@ -130,6 +141,11 @@ export default class {
130
141
  if (hasProperty(subdevice, 'scheduleBMode')) {
131
142
  this.mtsList.push(subdevice.id)
132
143
  }
144
+
145
+ // Check to see if any MST (sprinkler) exist
146
+ if (hasProperty(subdevice, 'mst')) {
147
+ this.waterList.push(subdevice.id)
148
+ }
133
149
  })
134
150
  }
135
151
 
@@ -240,6 +256,51 @@ export default class {
240
256
  })
241
257
  }
242
258
  }
259
+
260
+ // Request status for any MST (sprinkler) devices that exist
261
+ if (this.waterList.length > 0) {
262
+ const payload = {
263
+ control: this.waterList.map(id => ({ subId: id, channel: 0 })),
264
+ }
265
+
266
+ // Send the request
267
+ const res3 = await this.platform.sendUpdate(this.accessory, {
268
+ namespace: 'Appliance.Control.Water',
269
+ payload,
270
+ method: 'GET',
271
+ })
272
+
273
+ // Log the received data
274
+ this.accessory.logDebug(`${platformLang.incPoll}: ${JSON.stringify(res3.data)}`)
275
+
276
+ const data3 = res3.data.payload
277
+ if (data3.control && Array.isArray(data3.control)) {
278
+ data3.control.forEach((entry) => {
279
+ // Check whether the homebridge accessory this relates to exists
280
+ const subAcc = this.devicesInHB.get(
281
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + entry.subId),
282
+ )
283
+
284
+ // No need to continue if the accessory doesn't exist nor the receiver function
285
+ if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
286
+ return
287
+ }
288
+
289
+ const toReturn = {}
290
+ if (hasProperty(entry, 'onoff')) {
291
+ toReturn.onoff = entry.onoff
292
+ }
293
+ if (hasProperty(entry, 'dura')) {
294
+ toReturn.dura = entry.dura
295
+ }
296
+
297
+ // Apply the update
298
+ if (Object.keys(toReturn).length > 0) {
299
+ subAcc.control.applyUpdate(toReturn)
300
+ }
301
+ })
302
+ }
303
+ }
243
304
  })
244
305
  } catch (err) {
245
306
  const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
@@ -377,6 +438,34 @@ export default class {
377
438
  })
378
439
  }
379
440
 
441
+ // Water control updates (MST100 sprinkler)
442
+ if (data.control && Array.isArray(data.control)) {
443
+ data.control.forEach((entry) => {
444
+ // Check whether the homebridge accessory this relates to exists
445
+ const subAcc = this.devicesInHB.get(
446
+ this.platform.api.hap.uuid.generate(this.accessory.context.serialNumber + entry.subId),
447
+ )
448
+
449
+ // No need to continue if the accessory doesn't exist nor the receiver function
450
+ if (!subAcc || !subAcc.control || !subAcc.control.applyUpdate) {
451
+ return
452
+ }
453
+
454
+ const toReturn = {}
455
+ if (hasProperty(entry, 'onoff')) {
456
+ toReturn.onoff = entry.onoff
457
+ }
458
+ if (hasProperty(entry, 'dura')) {
459
+ toReturn.dura = entry.dura
460
+ }
461
+
462
+ // Apply the update
463
+ if (Object.keys(toReturn).length > 0) {
464
+ subAcc.control.applyUpdate(toReturn)
465
+ }
466
+ })
467
+ }
468
+
380
469
  // Leak sensor updates
381
470
  if (data.waterLeak && Array.isArray(data.waterLeak)) {
382
471
  data.waterLeak.forEach((entry) => {
@@ -0,0 +1,235 @@
1
+ import PQueue from 'p-queue'
2
+ import { TimeoutError } from 'p-timeout'
3
+
4
+ import platformConsts from '../utils/constants.js'
5
+ import { hasProperty, parseError } from '../utils/functions.js'
6
+ import platformLang from '../utils/lang-en.js'
7
+
8
+ export default class {
9
+ constructor(platform, accessory, priAcc) {
10
+ // Set up variables from the platform
11
+ this.hapChar = platform.api.hap.Characteristic
12
+ this.hapErr = platform.api.hap.HapStatusError
13
+ this.hapServ = platform.api.hap.Service
14
+ this.platform = platform
15
+
16
+ // Set up variables from the accessory
17
+ this.accessory = accessory
18
+ this.lowBattThreshold = accessory.context.options.lowBattThreshold
19
+ ? Math.min(accessory.context.options.lowBattThreshold, 100)
20
+ : platformConsts.defaultValues.lowBattThreshold
21
+ this.name = accessory.displayName
22
+ this.priAcc = priAcc
23
+
24
+ // Add the valve service if it doesn't already exist
25
+ this.service = this.accessory.getService(this.hapServ.Valve)
26
+ || this.accessory.addService(this.hapServ.Valve)
27
+
28
+ // Set the valve type to irrigation
29
+ this.service.getCharacteristic(this.hapChar.ValveType).updateValue(1)
30
+
31
+ // Set up Active characteristic
32
+ this.service
33
+ .getCharacteristic(this.hapChar.Active)
34
+ .onSet(async value => this.internalStateUpdate(value))
35
+ this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value
36
+
37
+ // Set up InUse characteristic (mirrors Active)
38
+ this.cacheInUse = this.service.getCharacteristic(this.hapChar.InUse).value
39
+
40
+ // Set up SetDuration characteristic
41
+ this.service
42
+ .getCharacteristic(this.hapChar.SetDuration)
43
+ .setProps({
44
+ minValue: 0,
45
+ maxValue: 43200,
46
+ minStep: 60,
47
+ })
48
+ .onSet(async value => this.internalDurationUpdate(value))
49
+ this.cacheDuration = this.service.getCharacteristic(this.hapChar.SetDuration).value || 3600
50
+
51
+ // Set up RemainingDuration characteristic
52
+ this.service
53
+ .getCharacteristic(this.hapChar.RemainingDuration)
54
+ .onGet(() => this.getRemainingDuration())
55
+
56
+ // Add the battery service if it doesn't already exist
57
+ this.battService = this.accessory.getService(this.hapServ.Battery)
58
+ || this.accessory.addService(this.hapServ.Battery)
59
+ this.cacheBatt = this.battService.getCharacteristic(this.hapChar.BatteryLevel).value
60
+
61
+ // Activation timestamp for remaining duration calculation
62
+ this.activationTime = 0
63
+ this.durationTimer = null
64
+
65
+ // Create the queue used for sending device requests
66
+ this.updateInProgress = false
67
+ this.queue = new PQueue({
68
+ concurrency: 1,
69
+ interval: 250,
70
+ intervalCap: 1,
71
+ timeout: 10000,
72
+ throwOnTimeout: true,
73
+ })
74
+ this.queue.on('idle', () => {
75
+ this.updateInProgress = false
76
+ })
77
+
78
+ // Output the customised options to the log
79
+ const opts = JSON.stringify({
80
+ connection: this.accessory.context.connection,
81
+ lowBattThreshold: this.lowBattThreshold,
82
+ })
83
+ platform.log('[%s] %s %s.', this.name, platformLang.devInitOpts, opts)
84
+ }
85
+
86
+ async internalStateUpdate(value) {
87
+ try {
88
+ // Add the request to the queue so updates are sent apart
89
+ await this.queue.add(async () => {
90
+ // Don't continue if the state is the same as before
91
+ if (value === this.cacheState) {
92
+ return
93
+ }
94
+
95
+ // This flag stops the plugin from requesting updates while pending on others
96
+ this.updateInProgress = true
97
+
98
+ // Generate the payload and namespace
99
+ // MST100 uses Appliance.Control.Water with onoff: 1=on, 2=off
100
+ const namespace = 'Appliance.Control.Water'
101
+ const payload = {
102
+ control: [
103
+ {
104
+ subId: this.accessory.context.subSerialNumber,
105
+ channel: 0,
106
+ onoff: value ? 1 : 2,
107
+ dura: value ? this.cacheDuration : 0,
108
+ },
109
+ ],
110
+ }
111
+
112
+ // Use the platform function to send the update to the device
113
+ await this.platform.sendUpdate(this.priAcc, {
114
+ namespace,
115
+ payload,
116
+ })
117
+
118
+ // Update the cache and log the update has been successful
119
+ this.cacheState = value
120
+ this.service.updateCharacteristic(this.hapChar.InUse, value)
121
+ this.cacheInUse = value
122
+
123
+ if (value) {
124
+ this.startDurationTimer()
125
+ } else {
126
+ this.clearDurationTimer()
127
+ }
128
+
129
+ this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
130
+ })
131
+ } catch (err) {
132
+ // Catch any errors whilst updating the device
133
+ const eText = err instanceof TimeoutError ? platformLang.timeout : parseError(err)
134
+ this.accessory.logWarn(`${platformLang.sendFailed} ${eText}`)
135
+ setTimeout(() => {
136
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState)
137
+ }, 2000)
138
+ throw new this.hapErr(-70402)
139
+ }
140
+ }
141
+
142
+ internalDurationUpdate(value) {
143
+ this.cacheDuration = value
144
+
145
+ // If currently active, restart the timer with the new duration
146
+ if (this.cacheState) {
147
+ this.startDurationTimer()
148
+ }
149
+ }
150
+
151
+ getRemainingDuration() {
152
+ if (!this.cacheState || !this.activationTime) {
153
+ return 0
154
+ }
155
+ const elapsed = Math.floor((Date.now() - this.activationTime) / 1000)
156
+ const remaining = this.cacheDuration - elapsed
157
+ return Math.max(0, remaining)
158
+ }
159
+
160
+ startDurationTimer() {
161
+ this.clearDurationTimer()
162
+ this.activationTime = Date.now()
163
+
164
+ if (this.cacheDuration > 0) {
165
+ this.durationTimer = setTimeout(() => {
166
+ // Auto-mark inactive when duration expires
167
+ this.cacheState = 0
168
+ this.cacheInUse = 0
169
+ this.service.updateCharacteristic(this.hapChar.Active, 0)
170
+ this.service.updateCharacteristic(this.hapChar.InUse, 0)
171
+ this.activationTime = 0
172
+ this.accessory.log(`${platformLang.curState} [off] (duration expired)`)
173
+ }, this.cacheDuration * 1000)
174
+ }
175
+ }
176
+
177
+ clearDurationTimer() {
178
+ if (this.durationTimer) {
179
+ clearTimeout(this.durationTimer)
180
+ this.durationTimer = null
181
+ }
182
+ this.activationTime = 0
183
+ }
184
+
185
+ applyUpdate(data) {
186
+ try {
187
+ // Handle onoff state (1=on, 2=off)
188
+ if (hasProperty(data, 'onoff')) {
189
+ const newState = data.onoff === 1 ? 1 : 0
190
+
191
+ if (this.cacheState !== newState) {
192
+ this.service.updateCharacteristic(this.hapChar.Active, newState)
193
+ this.service.updateCharacteristic(this.hapChar.InUse, newState)
194
+ this.cacheState = newState
195
+ this.cacheInUse = newState
196
+
197
+ if (newState) {
198
+ this.startDurationTimer()
199
+ } else {
200
+ this.clearDurationTimer()
201
+ }
202
+
203
+ this.accessory.log(`${platformLang.curState} [${newState ? 'on' : 'off'}]`)
204
+ }
205
+ }
206
+
207
+ // Handle duration
208
+ if (hasProperty(data, 'dura')) {
209
+ const newDura = data.dura
210
+ if (newDura > 0 && newDura !== this.cacheDuration) {
211
+ this.cacheDuration = newDura
212
+ this.service.updateCharacteristic(this.hapChar.SetDuration, newDura)
213
+ }
214
+ }
215
+
216
+ // Battery % from reported voltage
217
+ if (hasProperty(data, 'voltage')) {
218
+ // Scale from [2000, 3000] to [0, 100]
219
+ let newVoltage = Math.min(Math.max(data.voltage, 2000), 3000)
220
+ newVoltage = Math.round((newVoltage - 2000) / 10)
221
+
222
+ if (newVoltage !== this.cacheBatt) {
223
+ this.cacheBatt = newVoltage
224
+ this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
225
+ this.battService.updateCharacteristic(
226
+ this.hapChar.StatusLowBattery,
227
+ this.cacheBatt < this.lowBattThreshold ? 1 : 0,
228
+ )
229
+ }
230
+ }
231
+ } catch (err) {
232
+ this.accessory.logWarn(`${platformLang.refFailed} ${parseError(err)}`)
233
+ }
234
+ }
235
+ }
@@ -11,6 +11,7 @@ import deviceHubLeak from './hub-leak.js'
11
11
  import deviceHubMain from './hub-main.js'
12
12
  import deviceHubSensor from './hub-sensor.js'
13
13
  import deviceHubSmoke from './hub-smoke.js'
14
+ import deviceHubSprinkler from './hub-sprinkler.js'
14
15
  import deviceHubValve from './hub-valve.js'
15
16
  import deviceHumidifier from './humidifier.js'
16
17
  import deviceLightCCT from './light-cct.js'
@@ -43,6 +44,7 @@ export default {
43
44
  deviceHubMain,
44
45
  deviceHubSensor,
45
46
  deviceHubSmoke,
47
+ deviceHubSprinkler,
46
48
  deviceHubValve,
47
49
  deviceHumidifier,
48
50
  deviceLightCCT,
package/lib/platform.js CHANGED
@@ -29,7 +29,7 @@ export default class {
29
29
  try {
30
30
  this.api = api
31
31
  this.log = log
32
- this.isBeta = plugin.version.includes('beta')
32
+ this.isBeta = process.argv.includes('-D')
33
33
  this.cloudClient = false
34
34
  this.deviceConf = {}
35
35
  this.devicesInHB = new Map()
@@ -303,14 +303,6 @@ export default class {
303
303
  if (this.isBeta) {
304
304
  this.log.debug = this.log
305
305
  this.log.debugWarn = this.log.warn
306
-
307
- // Log that using a beta will generate a lot of debug logs
308
- if (this.isBeta) {
309
- const divide = '*'.repeat(platformLang.beta.length + 1) // don't forget the full stop (+1!)
310
- this.log.warn(divide)
311
- this.log.warn(`${platformLang.beta}.`)
312
- this.log.warn(divide)
313
- }
314
306
  } else {
315
307
  this.log.debug = () => {}
316
308
  this.log.debugWarn = () => {}
@@ -944,8 +936,13 @@ export default class {
944
936
  case 'MS400':
945
937
  subAcc.control = new deviceTypes.deviceHubLeak(this, subAcc)
946
938
  break
939
+ case 'MST100':
940
+ subAcc.control = new deviceTypes.deviceHubSprinkler(this, subAcc, accessory)
941
+ break
942
+ case 'MTS100':
947
943
  case 'MTS100V3':
948
944
  case 'MTS150':
945
+ case 'MTS150P':
949
946
  subAcc.control = new deviceTypes.deviceHubValve(this, subAcc, accessory)
950
947
  break
951
948
  default:
@@ -197,6 +197,7 @@ export default {
197
197
  models: {
198
198
  switchSingle: [
199
199
  'HP110A',
200
+ 'MSS100',
200
201
  'MSS105',
201
202
  'MSS110',
202
203
  'MSS115',
@@ -231,6 +232,9 @@ export default {
231
232
  'MSS710',
232
233
  'MSS710R',
233
234
  'MSS810',
235
+ 'P11',
236
+ 'R10',
237
+ 'R11',
234
238
  ],
235
239
  switchMulti: {
236
240
  MSP843P: 5,
@@ -256,7 +260,9 @@ export default {
256
260
  MSS620BR: 2,
257
261
  MSS620R: 2,
258
262
  MSS620S: 2,
263
+ MOP320: 3,
259
264
  MSS630: 3,
265
+ R21: 2,
260
266
  SP425EW: 4, // MSS425E
261
267
  SP425FW: 4, // MSS425F
262
268
  },
@@ -298,13 +304,13 @@ export default {
298
304
  diffuser: ['MOD100', 'MOD150'],
299
305
  purifier: ['MAP100'],
300
306
  humidifier: ['MSXH0'],
301
- garage: ['MSG100', 'MSG200'],
307
+ garage: ['MSG100', 'MSG150', 'MSG200'],
302
308
  roller: ['MRS100'],
303
309
  baby: ['HP110A', 'HP110AHK'],
304
- thermostat: ['MTS200', 'MTS200B', 'MTS960'],
310
+ thermostat: ['MTS200', 'MTS200B', 'MTS205', 'MTS960'],
305
311
  sensorPresence: ['MS600'],
306
- hubMain: ['MSH300', 'MSH300HK', 'MSH400'],
307
- hubSub: ['GS559A', 'MS100', 'MS100F', 'MS200', 'MS400', 'MTS100V3', 'MTS150'],
312
+ hubMain: ['MSH300', 'MSH300HK', 'MSH400', 'MSH450'],
313
+ hubSub: ['GS559A', 'MS100', 'MS100F', 'MS130', 'MS200', 'MS400', 'MST100', 'MTS100', 'MTS100V3', 'MTS150', 'MTS150P'],
308
314
  template: [],
309
315
  },
310
316
 
@@ -371,7 +377,7 @@ export default {
371
377
  MSS315: ['9'], // https://github.com/homebridge-plugins/homebridge-meross/issues/537
372
378
  },
373
379
 
374
- noLocalControl: ['MSH300', 'MSH300HK', 'MSH400'],
380
+ noLocalControl: ['MSH300', 'MSH300HK', 'MSH400', 'MSH450'],
375
381
 
376
382
  httpRetryCodes: ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN', 'ECONNABORTED'],
377
383
  }
@@ -7,7 +7,6 @@ export default {
7
7
  accTokenStoreErr: 'could not store access token as',
8
8
  accTokenUserChange: 'username has changed',
9
9
  alDisabled: 'adaptive lighting disabled due to change of colour detected',
10
- beta: 'You are using a beta version of the plugin - you will experience more logging than normal',
11
10
  brand: 'Meross Technology',
12
11
  cfgDef: 'is not a valid number so using default of',
13
12
  cfgDup: 'will be ignored since another entry with this ID already exists',
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.10.3",
5
+ "version": "10.12.1-beta.0",
6
6
  "description": "Homebridge plugin to integrate Meross devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",
@@ -56,14 +56,14 @@
56
56
  "lint:fix": "npm run lint -- --fix"
57
57
  },
58
58
  "dependencies": {
59
- "@homebridge/plugin-ui-utils": "^2.1.2",
60
- "axios": "^1.13.2",
61
- "mqtt": "^5.14.1",
59
+ "@homebridge/plugin-ui-utils": "^2.2.0",
60
+ "axios": "^1.13.6",
61
+ "mqtt": "^5.15.0",
62
62
  "node-persist": "^4.0.4",
63
- "p-queue": "^9.0.1",
63
+ "p-queue": "^9.1.0",
64
64
  "p-timeout": "^7.0.1"
65
65
  },
66
66
  "devDependencies": {
67
- "@antfu/eslint-config": "^6.4.2"
67
+ "@antfu/eslint-config": "^7.6.1"
68
68
  }
69
69
  }