meross-iot 0.1.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 +30 -0
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/index.d.ts +2344 -0
- package/index.js +131 -0
- package/lib/controller/device.js +1317 -0
- package/lib/controller/features/alarm-feature.js +89 -0
- package/lib/controller/features/child-lock-feature.js +61 -0
- package/lib/controller/features/config-feature.js +54 -0
- package/lib/controller/features/consumption-feature.js +210 -0
- package/lib/controller/features/control-feature.js +62 -0
- package/lib/controller/features/diffuser-feature.js +411 -0
- package/lib/controller/features/digest-timer-feature.js +22 -0
- package/lib/controller/features/digest-trigger-feature.js +22 -0
- package/lib/controller/features/dnd-feature.js +79 -0
- package/lib/controller/features/electricity-feature.js +144 -0
- package/lib/controller/features/encryption-feature.js +259 -0
- package/lib/controller/features/garage-feature.js +337 -0
- package/lib/controller/features/hub-feature.js +687 -0
- package/lib/controller/features/light-feature.js +408 -0
- package/lib/controller/features/presence-sensor-feature.js +297 -0
- package/lib/controller/features/roller-shutter-feature.js +456 -0
- package/lib/controller/features/runtime-feature.js +74 -0
- package/lib/controller/features/screen-feature.js +67 -0
- package/lib/controller/features/sensor-history-feature.js +47 -0
- package/lib/controller/features/smoke-config-feature.js +50 -0
- package/lib/controller/features/spray-feature.js +166 -0
- package/lib/controller/features/system-feature.js +269 -0
- package/lib/controller/features/temp-unit-feature.js +55 -0
- package/lib/controller/features/thermostat-feature.js +804 -0
- package/lib/controller/features/timer-feature.js +507 -0
- package/lib/controller/features/toggle-feature.js +223 -0
- package/lib/controller/features/trigger-feature.js +333 -0
- package/lib/controller/hub-device.js +185 -0
- package/lib/controller/subdevice.js +1537 -0
- package/lib/device-factory.js +463 -0
- package/lib/error-budget.js +138 -0
- package/lib/http-api.js +766 -0
- package/lib/manager.js +1609 -0
- package/lib/model/channel-info.js +79 -0
- package/lib/model/constants.js +119 -0
- package/lib/model/enums.js +819 -0
- package/lib/model/exception.js +363 -0
- package/lib/model/http/device.js +215 -0
- package/lib/model/http/error-codes.js +121 -0
- package/lib/model/http/exception.js +151 -0
- package/lib/model/http/subdevice.js +133 -0
- package/lib/model/push/alarm.js +112 -0
- package/lib/model/push/bind.js +97 -0
- package/lib/model/push/common.js +282 -0
- package/lib/model/push/diffuser-light.js +100 -0
- package/lib/model/push/diffuser-spray.js +83 -0
- package/lib/model/push/factory.js +229 -0
- package/lib/model/push/generic.js +115 -0
- package/lib/model/push/hub-battery.js +59 -0
- package/lib/model/push/hub-mts100-all.js +64 -0
- package/lib/model/push/hub-mts100-mode.js +59 -0
- package/lib/model/push/hub-mts100-temperature.js +62 -0
- package/lib/model/push/hub-online.js +59 -0
- package/lib/model/push/hub-sensor-alert.js +61 -0
- package/lib/model/push/hub-sensor-all.js +59 -0
- package/lib/model/push/hub-sensor-smoke.js +110 -0
- package/lib/model/push/hub-sensor-temphum.js +62 -0
- package/lib/model/push/hub-subdevicelist.js +50 -0
- package/lib/model/push/hub-togglex.js +60 -0
- package/lib/model/push/index.js +81 -0
- package/lib/model/push/online.js +53 -0
- package/lib/model/push/presence-study.js +61 -0
- package/lib/model/push/sensor-latestx.js +106 -0
- package/lib/model/push/timerx.js +63 -0
- package/lib/model/push/togglex.js +78 -0
- package/lib/model/push/triggerx.js +62 -0
- package/lib/model/push/unbind.js +34 -0
- package/lib/model/push/water-leak.js +107 -0
- package/lib/model/states/diffuser-light-state.js +119 -0
- package/lib/model/states/diffuser-spray-state.js +58 -0
- package/lib/model/states/garage-door-state.js +71 -0
- package/lib/model/states/index.js +38 -0
- package/lib/model/states/light-state.js +134 -0
- package/lib/model/states/presence-sensor-state.js +239 -0
- package/lib/model/states/roller-shutter-state.js +82 -0
- package/lib/model/states/spray-state.js +58 -0
- package/lib/model/states/thermostat-state.js +297 -0
- package/lib/model/states/timer-state.js +192 -0
- package/lib/model/states/toggle-state.js +105 -0
- package/lib/model/states/trigger-state.js +155 -0
- package/lib/subscription.js +587 -0
- package/lib/utilities/conversion.js +62 -0
- package/lib/utilities/debug.js +165 -0
- package/lib/utilities/mqtt.js +152 -0
- package/lib/utilities/network.js +53 -0
- package/lib/utilities/options.js +64 -0
- package/lib/utilities/request-queue.js +161 -0
- package/lib/utilities/ssid.js +37 -0
- package/lib/utilities/state-changes.js +66 -0
- package/lib/utilities/stats.js +687 -0
- package/lib/utilities/timer.js +310 -0
- package/lib/utilities/trigger.js +286 -0
- package/package.json +73 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps Meross API namespace strings to device feature modules.
|
|
5
|
+
*
|
|
6
|
+
* Used during device class construction to dynamically compose device classes
|
|
7
|
+
* based on the capabilities reported by each device. This avoids maintaining
|
|
8
|
+
* separate classes for every device type and version combination.
|
|
9
|
+
*
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
const ABILITY_MATRIX = {
|
|
13
|
+
// Power plugs abilities
|
|
14
|
+
'Appliance.Control.ToggleX': require('./controller/features/toggle-feature'),
|
|
15
|
+
'Appliance.Control.Toggle': require('./controller/features/toggle-feature'),
|
|
16
|
+
'Appliance.Control.ConsumptionX': require('./controller/features/consumption-feature'),
|
|
17
|
+
'Appliance.Control.Consumption': require('./controller/features/consumption-feature'),
|
|
18
|
+
'Appliance.Control.Electricity': require('./controller/features/electricity-feature'),
|
|
19
|
+
'Appliance.Control.Alarm': require('./controller/features/alarm-feature'),
|
|
20
|
+
|
|
21
|
+
// Timer and Trigger
|
|
22
|
+
'Appliance.Control.TimerX': require('./controller/features/timer-feature'),
|
|
23
|
+
'Appliance.Digest.TimerX': require('./controller/features/digest-timer-feature'),
|
|
24
|
+
'Appliance.Control.TriggerX': require('./controller/features/trigger-feature'),
|
|
25
|
+
'Appliance.Digest.TriggerX': require('./controller/features/digest-trigger-feature'),
|
|
26
|
+
|
|
27
|
+
// Light abilities
|
|
28
|
+
'Appliance.Control.Light': require('./controller/features/light-feature'),
|
|
29
|
+
|
|
30
|
+
// Garage opener
|
|
31
|
+
'Appliance.GarageDoor.State': require('./controller/features/garage-feature'),
|
|
32
|
+
|
|
33
|
+
// Roller shutter
|
|
34
|
+
'Appliance.RollerShutter.State': require('./controller/features/roller-shutter-feature'),
|
|
35
|
+
|
|
36
|
+
// Spray/Humidifier
|
|
37
|
+
'Appliance.Control.Spray': require('./controller/features/spray-feature'),
|
|
38
|
+
|
|
39
|
+
// Diffuser
|
|
40
|
+
'Appliance.Control.Diffuser.Light': require('./controller/features/diffuser-feature'),
|
|
41
|
+
'Appliance.Control.Diffuser.Spray': require('./controller/features/diffuser-feature'),
|
|
42
|
+
|
|
43
|
+
// Thermostat
|
|
44
|
+
'Appliance.Control.Thermostat.Mode': require('./controller/features/thermostat-feature'),
|
|
45
|
+
'Appliance.Control.Thermostat.ModeB': require('./controller/features/thermostat-feature'),
|
|
46
|
+
|
|
47
|
+
// System (always included via buildDevice, but listed here for completeness)
|
|
48
|
+
'Appliance.System.All': require('./controller/features/system-feature'),
|
|
49
|
+
'Appliance.System.Online': require('./controller/features/system-feature'),
|
|
50
|
+
'Appliance.System.Hardware': require('./controller/features/system-feature'),
|
|
51
|
+
'Appliance.System.Firmware': require('./controller/features/system-feature'),
|
|
52
|
+
'Appliance.System.Time': require('./controller/features/system-feature'),
|
|
53
|
+
'Appliance.System.Clock': require('./controller/features/system-feature'),
|
|
54
|
+
'Appliance.System.Position': require('./controller/features/system-feature'),
|
|
55
|
+
'Appliance.System.Ability': require('./controller/features/system-feature'),
|
|
56
|
+
'Appliance.System.Report': require('./controller/features/system-feature'),
|
|
57
|
+
'Appliance.System.Debug': require('./controller/features/system-feature'),
|
|
58
|
+
'Appliance.System.Factory': require('./controller/features/system-feature'),
|
|
59
|
+
'Appliance.System.LedMode': require('./controller/features/system-feature'),
|
|
60
|
+
'Appliance.Mcu.Firmware': require('./controller/features/system-feature'),
|
|
61
|
+
|
|
62
|
+
// Encryption
|
|
63
|
+
'Appliance.Encrypt.Suite': require('./controller/features/encryption-feature'),
|
|
64
|
+
'Appliance.Encrypt.ECDHE': require('./controller/features/encryption-feature'),
|
|
65
|
+
|
|
66
|
+
// DND
|
|
67
|
+
'Appliance.System.DNDMode': require('./controller/features/dnd-feature'),
|
|
68
|
+
|
|
69
|
+
// Runtime
|
|
70
|
+
'Appliance.System.Runtime': require('./controller/features/runtime-feature'),
|
|
71
|
+
|
|
72
|
+
// Hub functionality (all hub features combined in single file)
|
|
73
|
+
'Appliance.Hub.Online': require('./controller/features/hub-feature'),
|
|
74
|
+
'Appliance.Hub.ToggleX': require('./controller/features/hub-feature'),
|
|
75
|
+
'Appliance.Hub.Battery': require('./controller/features/hub-feature'),
|
|
76
|
+
'Appliance.Hub.Sensor.WaterLeak': require('./controller/features/hub-feature'),
|
|
77
|
+
'Appliance.Hub.Sensor.All': require('./controller/features/hub-feature'),
|
|
78
|
+
'Appliance.Hub.Sensor.TempHum': require('./controller/features/hub-feature'),
|
|
79
|
+
'Appliance.Hub.Sensor.Alert': require('./controller/features/hub-feature'),
|
|
80
|
+
'Appliance.Hub.Sensor.Smoke': require('./controller/features/hub-feature'),
|
|
81
|
+
'Appliance.Hub.Sensor.Adjust': require('./controller/features/hub-feature'),
|
|
82
|
+
'Appliance.Hub.Sensor.DoorWindow': require('./controller/features/hub-feature'),
|
|
83
|
+
'Appliance.Hub.Mts100.All': require('./controller/features/hub-feature'),
|
|
84
|
+
'Appliance.Hub.Mts100.Mode': require('./controller/features/hub-feature'),
|
|
85
|
+
'Appliance.Hub.Mts100.Temperature': require('./controller/features/hub-feature'),
|
|
86
|
+
'Appliance.Hub.Mts100.Adjust': require('./controller/features/hub-feature'),
|
|
87
|
+
'Appliance.Hub.Mts100.SuperCtl': require('./controller/features/hub-feature'),
|
|
88
|
+
'Appliance.Hub.Mts100.ScheduleB': require('./controller/features/hub-feature'),
|
|
89
|
+
'Appliance.Hub.Mts100.Config': require('./controller/features/hub-feature'),
|
|
90
|
+
'Appliance.Hub.Exception': require('./controller/features/hub-feature'),
|
|
91
|
+
'Appliance.Hub.Report': require('./controller/features/hub-feature'),
|
|
92
|
+
'Appliance.Hub.PairSubDev': require('./controller/features/hub-feature'),
|
|
93
|
+
'Appliance.Hub.SubDevice.Beep': require('./controller/features/hub-feature'),
|
|
94
|
+
'Appliance.Hub.SubDevice.MotorAdjust': require('./controller/features/hub-feature'),
|
|
95
|
+
'Appliance.Hub.SubDevice.Version': require('./controller/features/hub-feature'),
|
|
96
|
+
|
|
97
|
+
// Roller Shutter
|
|
98
|
+
'Appliance.RollerShutter.Position': require('./controller/features/roller-shutter-feature'),
|
|
99
|
+
'Appliance.RollerShutter.Config': require('./controller/features/roller-shutter-feature'),
|
|
100
|
+
'Appliance.RollerShutter.Adjust': require('./controller/features/roller-shutter-feature'),
|
|
101
|
+
|
|
102
|
+
// Thermostat additional namespaces
|
|
103
|
+
'Appliance.Control.Thermostat.Schedule': require('./controller/features/thermostat-feature'),
|
|
104
|
+
'Appliance.Control.Thermostat.Timer': require('./controller/features/thermostat-feature'),
|
|
105
|
+
'Appliance.Control.Thermostat.Alarm': require('./controller/features/thermostat-feature'),
|
|
106
|
+
'Appliance.Control.Thermostat.WindowOpened': require('./controller/features/thermostat-feature'),
|
|
107
|
+
'Appliance.Control.Thermostat.HoldAction': require('./controller/features/thermostat-feature'),
|
|
108
|
+
'Appliance.Control.Thermostat.Overheat': require('./controller/features/thermostat-feature'),
|
|
109
|
+
'Appliance.Control.Thermostat.DeadZone': require('./controller/features/thermostat-feature'),
|
|
110
|
+
'Appliance.Control.Thermostat.Calibration': require('./controller/features/thermostat-feature'),
|
|
111
|
+
'Appliance.Control.Thermostat.Sensor': require('./controller/features/thermostat-feature'),
|
|
112
|
+
'Appliance.Control.Thermostat.SummerMode': require('./controller/features/thermostat-feature'),
|
|
113
|
+
'Appliance.Control.Thermostat.Frost': require('./controller/features/thermostat-feature'),
|
|
114
|
+
'Appliance.Control.Thermostat.AlarmConfig': require('./controller/features/thermostat-feature'),
|
|
115
|
+
'Appliance.Control.Thermostat.CompressorDelay': require('./controller/features/thermostat-feature'),
|
|
116
|
+
'Appliance.Control.Thermostat.CtlRange': require('./controller/features/thermostat-feature'),
|
|
117
|
+
|
|
118
|
+
// Config namespaces
|
|
119
|
+
'Appliance.Config.OverTemp': require('./controller/features/config-feature'),
|
|
120
|
+
|
|
121
|
+
// Control namespaces
|
|
122
|
+
'Appliance.Control.Multiple': require('./controller/features/control-feature'),
|
|
123
|
+
'Appliance.Control.Upgrade': require('./controller/features/control-feature'),
|
|
124
|
+
'Appliance.Control.OverTemp': require('./controller/features/control-feature'),
|
|
125
|
+
'Appliance.Control.ConsumptionConfig': require('./controller/features/consumption-feature'),
|
|
126
|
+
'Appliance.Control.Diffuser.Sensor': require('./controller/features/diffuser-feature'),
|
|
127
|
+
'Appliance.Control.PhysicalLock': require('./controller/features/child-lock-feature'),
|
|
128
|
+
'Appliance.Control.Screen.Brightness': require('./controller/features/screen-feature'),
|
|
129
|
+
'Appliance.Control.Sensor.History': require('./controller/features/sensor-history-feature'),
|
|
130
|
+
'Appliance.Control.Sensor.LatestX': require('./controller/features/presence-sensor-feature'),
|
|
131
|
+
'Appliance.Control.Smoke.Config': require('./controller/features/smoke-config-feature'),
|
|
132
|
+
'Appliance.Control.TempUnit': require('./controller/features/temp-unit-feature'),
|
|
133
|
+
|
|
134
|
+
// Presence sensor
|
|
135
|
+
'Appliance.Control.Presence.Config': require('./controller/features/presence-sensor-feature'),
|
|
136
|
+
'Appliance.Control.Presence.Study': require('./controller/features/presence-sensor-feature'),
|
|
137
|
+
|
|
138
|
+
// Garage door additional namespaces
|
|
139
|
+
'Appliance.GarageDoor.MultipleConfig': require('./controller/features/garage-feature'),
|
|
140
|
+
'Appliance.GarageDoor.Config': require('./controller/features/garage-feature')
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Namespace that identifies hub devices.
|
|
145
|
+
*
|
|
146
|
+
* Used to distinguish hub devices from regular devices during device creation,
|
|
147
|
+
* since hubs require a different base class and constructor signature.
|
|
148
|
+
*
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
151
|
+
const HUB_DISCRIMINATING_ABILITY = 'Appliance.Hub.SubdeviceList';
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Cache for dynamically created device classes.
|
|
155
|
+
*
|
|
156
|
+
* Caching avoids recreating identical classes for devices with the same type and versions,
|
|
157
|
+
* reducing memory usage and improving instantiation performance.
|
|
158
|
+
*
|
|
159
|
+
* @type {Map<string, Function>}
|
|
160
|
+
* Key: type string (e.g., "mss310:1.0.0:4.2.1")
|
|
161
|
+
* Value: Device class constructor
|
|
162
|
+
*/
|
|
163
|
+
const _dynamicTypes = new Map();
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Generates a cache key from device type and version information.
|
|
167
|
+
*
|
|
168
|
+
* The key format `deviceType:hardwareVersion:firmwareVersion` ensures devices with
|
|
169
|
+
* different capabilities (due to version differences) get separate cached classes.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} deviceType - Device type (e.g., "mss310", "msl120")
|
|
172
|
+
* @param {string} [hardwareVersion] - Hardware version (e.g., "1.0.0"). Defaults to 'unknown' if not provided.
|
|
173
|
+
* @param {string} [firmwareVersion] - Firmware version (e.g., "4.2.1"). Defaults to 'unknown' if not provided.
|
|
174
|
+
* @returns {string} Cache key string (e.g., "mss310:1.0.0:4.2.1")
|
|
175
|
+
*/
|
|
176
|
+
function getTypeKey(deviceType, hardwareVersion, firmwareVersion) {
|
|
177
|
+
const hw = hardwareVersion || 'unknown';
|
|
178
|
+
const fw = firmwareVersion || 'unknown';
|
|
179
|
+
return `${deviceType}:${hw}:${fw}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Retrieves a cached device class if one exists for the given type and versions.
|
|
184
|
+
*
|
|
185
|
+
* Device classes are cached to avoid recreating identical classes for devices
|
|
186
|
+
* with the same capabilities, reducing memory usage and improving performance.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} deviceType - Device type (e.g., "mss310")
|
|
189
|
+
* @param {string} [hardwareVersion] - Hardware version (e.g., "1.0.0")
|
|
190
|
+
* @param {string} [firmwareVersion] - Firmware version (e.g., "4.2.1")
|
|
191
|
+
* @returns {Function|null} Cached device class constructor, or null if not found in cache
|
|
192
|
+
*/
|
|
193
|
+
function getCachedDeviceClass(deviceType, hardwareVersion, firmwareVersion) {
|
|
194
|
+
const typeKey = getTypeKey(deviceType, hardwareVersion, firmwareVersion);
|
|
195
|
+
return _dynamicTypes.get(typeKey) || null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Builds a device class dynamically by composing features based on device abilities.
|
|
200
|
+
*
|
|
201
|
+
* Features are selected from the ability matrix and applied to a base class. Handlers
|
|
202
|
+
* are chained so multiple features can process the same events or state updates.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} typeKey - Type key for caching
|
|
205
|
+
* @param {Object} abilities - Device abilities dictionary
|
|
206
|
+
* @param {Function} BaseClass - Base class (MerossDevice or MerossHubDevice)
|
|
207
|
+
* @returns {Function} Dynamic device class
|
|
208
|
+
*/
|
|
209
|
+
function _buildDynamicClass(typeKey, abilities, BaseClass) {
|
|
210
|
+
const features = new Set();
|
|
211
|
+
|
|
212
|
+
// System feature provides core device functionality required by all devices
|
|
213
|
+
features.add(require('./controller/features/system-feature'));
|
|
214
|
+
|
|
215
|
+
if (abilities && typeof abilities === 'object') {
|
|
216
|
+
// X-suffixed namespaces are extended versions that replace base namespaces
|
|
217
|
+
// Track base namespaces to avoid adding both base and X versions
|
|
218
|
+
const hasXVersion = new Set();
|
|
219
|
+
for (const namespace of Object.keys(abilities)) {
|
|
220
|
+
if (namespace.endsWith('X')) {
|
|
221
|
+
const baseNamespace = namespace.slice(0, -1);
|
|
222
|
+
hasXVersion.add(baseNamespace);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Prefer X versions over base versions to use the most capable feature implementation
|
|
227
|
+
for (const [namespace] of Object.entries(abilities)) {
|
|
228
|
+
const feature = ABILITY_MATRIX[namespace];
|
|
229
|
+
if (feature) {
|
|
230
|
+
if (namespace.endsWith('X')) {
|
|
231
|
+
features.add(feature);
|
|
232
|
+
} else {
|
|
233
|
+
// Only add base version if no X version exists
|
|
234
|
+
if (!hasXVersion.has(namespace)) {
|
|
235
|
+
features.add(feature);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
class DynamicDevice extends BaseClass {}
|
|
243
|
+
|
|
244
|
+
const featureArray = Array.from(features);
|
|
245
|
+
const pushHandlers = [];
|
|
246
|
+
const refreshHandlers = [];
|
|
247
|
+
|
|
248
|
+
// Separate handlers from other properties so they can be chained together.
|
|
249
|
+
// Features are applied at class creation time, so no existing handlers need
|
|
250
|
+
// to be preserved. This allows multiple features to handle the same namespace
|
|
251
|
+
// or participate in state refresh operations.
|
|
252
|
+
for (const feature of featureArray) {
|
|
253
|
+
if (feature.handlePushNotification) {
|
|
254
|
+
pushHandlers.push(feature.handlePushNotification);
|
|
255
|
+
}
|
|
256
|
+
if (feature.refreshState) {
|
|
257
|
+
refreshHandlers.push(feature.refreshState);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const { handlePushNotification: _handlePushNotification, refreshState: _refreshState, ...featureProperties } = feature;
|
|
261
|
+
Object.assign(DynamicDevice.prototype, featureProperties);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Chain push handlers so multiple features can process notifications.
|
|
265
|
+
// The first handler that returns true stops the chain to avoid duplicate processing.
|
|
266
|
+
if (pushHandlers.length > 0) {
|
|
267
|
+
DynamicDevice.prototype.handlePushNotification = function (namespace, data) {
|
|
268
|
+
for (const handler of pushHandlers) {
|
|
269
|
+
if (handler.call(this, namespace, data)) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Chain refresh handlers so all features can update their state.
|
|
278
|
+
// All handlers are called sequentially since state refresh operations
|
|
279
|
+
// may depend on each other and must complete in order.
|
|
280
|
+
if (refreshHandlers.length > 0) {
|
|
281
|
+
DynamicDevice.prototype.refreshState = async function (timeout = null) {
|
|
282
|
+
for (const handler of refreshHandlers) {
|
|
283
|
+
await handler.call(this, timeout);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return DynamicDevice;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Builds a device instance from device info and abilities.
|
|
293
|
+
*
|
|
294
|
+
* Uses cached device classes when available, or dynamically creates a new class
|
|
295
|
+
* that includes only the features supported by the device's abilities. Hub devices
|
|
296
|
+
* are detected by the presence of a specific ability and use a different base class.
|
|
297
|
+
*
|
|
298
|
+
* @param {Object} deviceInfo - Device info from HTTP API (contains deviceType, hdwareVersion, fmwareVersion, etc.)
|
|
299
|
+
* @param {Object} abilities - Device abilities dictionary from `Appliance.System.Ability` namespace
|
|
300
|
+
* @param {MerossCloud} manager - MerossCloud manager instance
|
|
301
|
+
* @param {Array<Object>} [subDeviceList] - Optional list of subdevices for hub devices
|
|
302
|
+
* @returns {MerossDevice|MerossHubDevice} Device instance with appropriate features applied
|
|
303
|
+
*/
|
|
304
|
+
function buildDevice(deviceInfo, abilities, manager, subDeviceList) {
|
|
305
|
+
const { deviceType } = deviceInfo;
|
|
306
|
+
const hardwareVersion = deviceInfo.hdwareVersion;
|
|
307
|
+
const firmwareVersion = deviceInfo.fmwareVersion;
|
|
308
|
+
|
|
309
|
+
// Hub detection must happen before class selection since hubs use a different base class
|
|
310
|
+
const isHub = abilities && typeof abilities === 'object' &&
|
|
311
|
+
HUB_DISCRIMINATING_ABILITY in abilities;
|
|
312
|
+
|
|
313
|
+
let DeviceClass = getCachedDeviceClass(deviceType, hardwareVersion, firmwareVersion);
|
|
314
|
+
|
|
315
|
+
if (!DeviceClass) {
|
|
316
|
+
// Lazy import device classes to avoid circular dependency
|
|
317
|
+
const { MerossDevice } = require('./controller/device');
|
|
318
|
+
const { MerossHubDevice } = require('./controller/hub-device');
|
|
319
|
+
|
|
320
|
+
const BaseClass = isHub ? MerossHubDevice : MerossDevice;
|
|
321
|
+
|
|
322
|
+
const typeKey = getTypeKey(deviceType, hardwareVersion, firmwareVersion);
|
|
323
|
+
DeviceClass = _buildDynamicClass(typeKey, abilities, BaseClass);
|
|
324
|
+
|
|
325
|
+
_dynamicTypes.set(typeKey, DeviceClass);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (isHub) {
|
|
329
|
+
return new DeviceClass(manager, deviceInfo, subDeviceList);
|
|
330
|
+
} else {
|
|
331
|
+
return new DeviceClass(manager, deviceInfo);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Maps subdevice type identifiers to their corresponding device class constructors.
|
|
337
|
+
*
|
|
338
|
+
* Different subdevice types require different class implementations because they
|
|
339
|
+
* expose different capabilities and API namespaces. This mapping enables the factory
|
|
340
|
+
* to instantiate the appropriate specialized class for each subdevice type.
|
|
341
|
+
*
|
|
342
|
+
* @private
|
|
343
|
+
*/
|
|
344
|
+
const SUBDEVICE_MAPPING = {
|
|
345
|
+
'mts100v3': require('./controller/subdevice').HubThermostatValve,
|
|
346
|
+
'ms100': require('./controller/subdevice').HubTempHumSensor,
|
|
347
|
+
'ms100f': require('./controller/subdevice').HubTempHumSensor,
|
|
348
|
+
'ms130': require('./controller/subdevice').HubTempHumSensor,
|
|
349
|
+
'ms405': require('./controller/subdevice').HubWaterLeakSensor,
|
|
350
|
+
'ms400': require('./controller/subdevice').HubWaterLeakSensor,
|
|
351
|
+
'ma151': require('./controller/subdevice').HubSmokeDetector
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Maps subdevice types to the hub ability namespaces they require.
|
|
356
|
+
*
|
|
357
|
+
* Hubs expose abilities for all their subdevices, but each subdevice only needs
|
|
358
|
+
* access to namespaces relevant to its type. This mapping filters the hub's full
|
|
359
|
+
* ability set to prevent subdevices from accessing capabilities they don't support,
|
|
360
|
+
* reducing API surface and potential misuse.
|
|
361
|
+
*
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
const SUBDEVICE_ABILITY_MAPPING = {
|
|
365
|
+
// Temperature/Humidity sensors
|
|
366
|
+
'ms100': ['Appliance.Hub.Sensor.TempHum', 'Appliance.Hub.Sensor.All'],
|
|
367
|
+
'ms100f': ['Appliance.Hub.Sensor.TempHum', 'Appliance.Hub.Sensor.All'],
|
|
368
|
+
'ms130': ['Appliance.Hub.Sensor.TempHum', 'Appliance.Hub.Sensor.All'],
|
|
369
|
+
// Smoke detectors
|
|
370
|
+
'ma151': ['Appliance.Hub.Sensor.Smoke', 'Appliance.Hub.Sensor.All'],
|
|
371
|
+
// Water leak sensors
|
|
372
|
+
'ms405': ['Appliance.Hub.Sensor.WaterLeak', 'Appliance.Hub.Sensor.All'],
|
|
373
|
+
'ms400': ['Appliance.Hub.Sensor.WaterLeak', 'Appliance.Hub.Sensor.All'],
|
|
374
|
+
// Thermostat valves
|
|
375
|
+
'mts100v3': ['Appliance.Hub.Mts100.All', 'Appliance.Hub.Mts100.Temperature', 'Appliance.Hub.Mts100.Mode', 'Appliance.Hub.Mts100.Adjust']
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Creates a subdevice instance from subdevice information.
|
|
380
|
+
*
|
|
381
|
+
* Instantiates the appropriate subdevice class based on the subdevice type to ensure
|
|
382
|
+
* type-specific functionality is available. Falls back to the generic MerossSubDevice
|
|
383
|
+
* class for unknown types to maintain compatibility with new or unsupported subdevice
|
|
384
|
+
* types without breaking the application.
|
|
385
|
+
*
|
|
386
|
+
* @param {Object|HttpSubdeviceInfo} subdeviceInfo - Subdevice info from HTTP API (contains subDeviceType/type, id, etc.) or HttpSubdeviceInfo instance
|
|
387
|
+
* @param {string} hubUuid - UUID of the hub device that owns this subdevice
|
|
388
|
+
* @param {Object} hubAbilities - Hub's abilities dictionary (currently unused, reserved for future use)
|
|
389
|
+
* @param {MerossCloud} manager - MerossCloud manager instance
|
|
390
|
+
* @returns {MerossSubDevice} Subdevice instance (specific subclass or generic MerossSubDevice)
|
|
391
|
+
*/
|
|
392
|
+
function buildSubdevice(subdeviceInfo, hubUuid, hubAbilities, manager) {
|
|
393
|
+
const HttpSubdeviceInfo = require('./model/http/subdevice');
|
|
394
|
+
const { MerossSubDevice } = require('./controller/subdevice');
|
|
395
|
+
|
|
396
|
+
const httpSubdeviceInfo = subdeviceInfo instanceof HttpSubdeviceInfo
|
|
397
|
+
? subdeviceInfo
|
|
398
|
+
: HttpSubdeviceInfo.fromDict(subdeviceInfo);
|
|
399
|
+
|
|
400
|
+
const subdeviceId = httpSubdeviceInfo.subDeviceId;
|
|
401
|
+
const subdeviceType = httpSubdeviceInfo.subDeviceType || '';
|
|
402
|
+
const normalizedType = subdeviceType.toLowerCase();
|
|
403
|
+
|
|
404
|
+
const SubdeviceClass = SUBDEVICE_MAPPING[normalizedType];
|
|
405
|
+
|
|
406
|
+
const kwargs = {
|
|
407
|
+
subDeviceType: httpSubdeviceInfo.subDeviceType,
|
|
408
|
+
subDeviceName: httpSubdeviceInfo.subDeviceName
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
if (!SubdeviceClass) {
|
|
412
|
+
const logger = manager?.options?.logger || console.warn;
|
|
413
|
+
logger(`Unknown subdevice type: ${subdeviceType}. Using generic MerossSubDevice`);
|
|
414
|
+
return new MerossSubDevice(hubUuid, subdeviceId, manager, kwargs);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return new SubdeviceClass(hubUuid, subdeviceId, manager, kwargs);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Filters hub abilities to only those relevant for a specific subdevice type.
|
|
422
|
+
*
|
|
423
|
+
* Hubs expose abilities for all their subdevices, but each subdevice should only
|
|
424
|
+
* access namespaces it supports. This filtering prevents subdevices from attempting
|
|
425
|
+
* to use unsupported APIs and reduces the ability surface exposed to each subdevice.
|
|
426
|
+
*
|
|
427
|
+
* @param {string} subdeviceType - Subdevice type (e.g., 'ms130', 'ma151', 'mts100v3')
|
|
428
|
+
* @param {Object} hubAbilities - Hub's full abilities dictionary from `Appliance.System.Ability`
|
|
429
|
+
* @returns {Object} Filtered abilities object containing only namespaces relevant to the subdevice type
|
|
430
|
+
*/
|
|
431
|
+
function getSubdeviceAbilities(subdeviceType, hubAbilities) {
|
|
432
|
+
if (!hubAbilities || typeof hubAbilities !== 'object') {
|
|
433
|
+
return {};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const normalizedType = (subdeviceType || '').toLowerCase();
|
|
437
|
+
const relevantNamespaces = SUBDEVICE_ABILITY_MAPPING[normalizedType];
|
|
438
|
+
|
|
439
|
+
if (!relevantNamespaces) {
|
|
440
|
+
return {};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const subdeviceAbilities = {};
|
|
444
|
+
for (const namespace of relevantNamespaces) {
|
|
445
|
+
if (hubAbilities[namespace]) {
|
|
446
|
+
subdeviceAbilities[namespace] = hubAbilities[namespace];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return subdeviceAbilities;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
module.exports = {
|
|
454
|
+
getTypeKey,
|
|
455
|
+
getCachedDeviceClass,
|
|
456
|
+
buildDevice,
|
|
457
|
+
HUB_DISCRIMINATING_ABILITY,
|
|
458
|
+
SUBDEVICE_MAPPING,
|
|
459
|
+
SUBDEVICE_ABILITY_MAPPING,
|
|
460
|
+
buildSubdevice,
|
|
461
|
+
getSubdeviceAbilities
|
|
462
|
+
};
|
|
463
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents an error budget for a single device
|
|
5
|
+
*
|
|
6
|
+
* Tracks the number of errors allowed within a time window. The budget decreases
|
|
7
|
+
* as errors occur and resets when the time window expires.
|
|
8
|
+
*
|
|
9
|
+
* @class ErrorBudget
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
class ErrorBudget {
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new ErrorBudget instance
|
|
15
|
+
*
|
|
16
|
+
* @param {number} initialBudget - Initial error budget (number of errors allowed)
|
|
17
|
+
* @param {number} windowStart - Timestamp when the time window started (milliseconds)
|
|
18
|
+
*/
|
|
19
|
+
constructor(initialBudget, windowStart) {
|
|
20
|
+
this.budget = initialBudget;
|
|
21
|
+
this.windowStart = windowStart;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Manages error budgets for multiple devices
|
|
27
|
+
*
|
|
28
|
+
* Tracks error budgets per device to prevent excessive errors from overwhelming
|
|
29
|
+
* devices or causing rate limiting. When a device's error budget is exhausted,
|
|
30
|
+
* LAN HTTP communication is disabled for that device until the time window expires.
|
|
31
|
+
*
|
|
32
|
+
* @class ErrorBudgetManager
|
|
33
|
+
*/
|
|
34
|
+
class ErrorBudgetManager {
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new ErrorBudgetManager instance
|
|
37
|
+
*
|
|
38
|
+
* @param {number} [maxErrors=1] - Maximum number of errors allowed per device per time window
|
|
39
|
+
* @param {number} [timeWindowMs=60000] - Time window in milliseconds (default: 60 seconds)
|
|
40
|
+
*/
|
|
41
|
+
constructor(maxErrors = 1, timeWindowMs = 60000) {
|
|
42
|
+
this._devicesBudget = new Map();
|
|
43
|
+
this._window = timeWindowMs;
|
|
44
|
+
this._maxErrors = maxErrors;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Gets or creates an error budget for a device and updates the time window if expired
|
|
49
|
+
*
|
|
50
|
+
* Resets the budget when the time window expires to allow the device to recover
|
|
51
|
+
* from temporary error conditions. This prevents permanent blacklisting of devices
|
|
52
|
+
* that experience transient network issues.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} deviceUuid - Device UUID
|
|
55
|
+
* @returns {ErrorBudget} Error budget for the device
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
_getUpdateBudgetWindow(deviceUuid) {
|
|
59
|
+
let devBudget = this._devicesBudget.get(deviceUuid);
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
|
|
62
|
+
if (!devBudget) {
|
|
63
|
+
devBudget = new ErrorBudget(this._maxErrors, now);
|
|
64
|
+
this._devicesBudget.set(deviceUuid, devBudget);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (now > (devBudget.windowStart + this._window)) {
|
|
68
|
+
devBudget.budget = this._maxErrors;
|
|
69
|
+
devBudget.windowStart = now;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return devBudget;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Notifies that an error occurred for a device
|
|
77
|
+
*
|
|
78
|
+
* Decrements the device's error budget. Once exhausted, LAN HTTP communication
|
|
79
|
+
* is disabled for the device until the time window resets, preventing further
|
|
80
|
+
* failed requests that could cause rate limiting or device instability.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} deviceUuid - Device UUID
|
|
83
|
+
* @returns {void}
|
|
84
|
+
*/
|
|
85
|
+
notifyError(deviceUuid) {
|
|
86
|
+
const devBudget = this._getUpdateBudgetWindow(deviceUuid);
|
|
87
|
+
if (devBudget.budget < 1) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
devBudget.budget -= 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Checks if a device's error budget is exhausted.
|
|
95
|
+
*
|
|
96
|
+
* Used to determine whether LAN HTTP communication should be disabled for
|
|
97
|
+
* a device to prevent further failed requests that could cause rate limiting
|
|
98
|
+
* or device instability.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} deviceUuid - Device UUID
|
|
101
|
+
* @returns {boolean} True if error budget is exhausted, false otherwise
|
|
102
|
+
*/
|
|
103
|
+
isOutOfBudget(deviceUuid) {
|
|
104
|
+
const budget = this._getUpdateBudgetWindow(deviceUuid);
|
|
105
|
+
return budget.budget < 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Gets the current error budget for a device.
|
|
110
|
+
*
|
|
111
|
+
* Useful for monitoring or debugging purposes to see how many errors remain
|
|
112
|
+
* before LAN HTTP communication is disabled for the device.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} deviceUuid - Device UUID
|
|
115
|
+
* @returns {number} Current error budget (number of errors remaining)
|
|
116
|
+
*/
|
|
117
|
+
getBudget(deviceUuid) {
|
|
118
|
+
const budget = this._getUpdateBudgetWindow(deviceUuid);
|
|
119
|
+
return budget.budget;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Resets the error budget for a device
|
|
124
|
+
*
|
|
125
|
+
* Removes the device's error budget entry, immediately re-enabling LAN HTTP
|
|
126
|
+
* communication. Used when manual intervention is needed to recover from
|
|
127
|
+
* error budget exhaustion.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} deviceUuid - Device UUID
|
|
130
|
+
* @returns {void}
|
|
131
|
+
*/
|
|
132
|
+
resetBudget(deviceUuid) {
|
|
133
|
+
this._devicesBudget.delete(deviceUuid);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = ErrorBudgetManager;
|
|
138
|
+
|