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,363 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base error class for all Meross errors.
|
|
5
|
+
*
|
|
6
|
+
* Provides a common interface for error handling with errorCode property to
|
|
7
|
+
* identify specific API error conditions. All library-specific errors extend
|
|
8
|
+
* this class to enable instanceof checks and consistent error handling
|
|
9
|
+
* throughout the library.
|
|
10
|
+
*
|
|
11
|
+
* @class
|
|
12
|
+
* @extends Error
|
|
13
|
+
* @property {number|null} errorCode - API error code (if available)
|
|
14
|
+
*/
|
|
15
|
+
class MerossError extends Error {
|
|
16
|
+
constructor(message, errorCode = null) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = this.constructor.name;
|
|
19
|
+
this.errorCode = errorCode;
|
|
20
|
+
Error.captureStackTrace(this, this.constructor);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Authentication related errors (error codes 1000-1008).
|
|
26
|
+
*
|
|
27
|
+
* Thrown when authentication fails due to invalid credentials, account issues,
|
|
28
|
+
* or authentication service problems. Indicates the login request cannot be
|
|
29
|
+
* completed with the provided credentials.
|
|
30
|
+
*
|
|
31
|
+
* Base class for authentication errors. HTTP-specific auth errors are in
|
|
32
|
+
* lib/model/http/exception.js.
|
|
33
|
+
*
|
|
34
|
+
* @class
|
|
35
|
+
* @extends MerossError
|
|
36
|
+
* @property {number} errorCode - API error code (1000-1008)
|
|
37
|
+
*/
|
|
38
|
+
class AuthenticationError extends MerossError {
|
|
39
|
+
constructor(message, errorCode) {
|
|
40
|
+
super(message || 'Authentication failed', errorCode);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* API limit reached error (error code 1042).
|
|
46
|
+
*
|
|
47
|
+
* Thrown when the API rate limit has been exceeded. Meross enforces rate limits
|
|
48
|
+
* to prevent abuse and ensure service stability. Requests should be throttled
|
|
49
|
+
* to stay within the allowed rate. Retry after a delay or reduce request frequency.
|
|
50
|
+
*
|
|
51
|
+
* @class
|
|
52
|
+
* @extends MerossError
|
|
53
|
+
* @property {number} errorCode - Always 1042
|
|
54
|
+
*/
|
|
55
|
+
class ApiLimitReachedError extends MerossError {
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super(message || 'API top limit reached', 1042);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resource access denied error (error code 1043).
|
|
63
|
+
*
|
|
64
|
+
* Thrown when access to a resource is denied due to insufficient permissions.
|
|
65
|
+
* The account may not have access to the requested device or resource, or the
|
|
66
|
+
* resource may not exist. Verify the resource exists and the account has
|
|
67
|
+
* appropriate permissions.
|
|
68
|
+
*
|
|
69
|
+
* @class
|
|
70
|
+
* @extends MerossError
|
|
71
|
+
* @property {number} errorCode - Always 1043
|
|
72
|
+
*/
|
|
73
|
+
class ResourceAccessDeniedError extends MerossError {
|
|
74
|
+
constructor(message) {
|
|
75
|
+
super(message || 'Resource access deny', 1043);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Device command timeout error.
|
|
81
|
+
*
|
|
82
|
+
* Thrown when a device command doesn't receive a response within the timeout period.
|
|
83
|
+
* Indicates the device may be offline, unreachable, or experiencing network issues
|
|
84
|
+
* that prevent timely communication. Check device connectivity and network status.
|
|
85
|
+
*
|
|
86
|
+
* @class
|
|
87
|
+
* @extends MerossError
|
|
88
|
+
* @property {string|null} deviceUuid - UUID of the device that timed out
|
|
89
|
+
* @property {number|null} timeout - Timeout duration in milliseconds
|
|
90
|
+
* @property {Object|null} command - Command information (method, namespace, etc.)
|
|
91
|
+
*/
|
|
92
|
+
class CommandTimeoutError extends MerossError {
|
|
93
|
+
constructor(message, deviceUuid = null, timeout = null, command = null) {
|
|
94
|
+
super(message || 'Command timeout occurred');
|
|
95
|
+
this.deviceUuid = deviceUuid;
|
|
96
|
+
this.timeout = timeout;
|
|
97
|
+
this.command = command;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Device command error (device returned an error response).
|
|
103
|
+
*
|
|
104
|
+
* Thrown when a device command fails and the device returns an error response.
|
|
105
|
+
* The command was received and processed by the device, but the device rejected
|
|
106
|
+
* it or encountered an error during execution. Check the errorPayload for
|
|
107
|
+
* device-specific error details.
|
|
108
|
+
*
|
|
109
|
+
* @class
|
|
110
|
+
* @extends MerossError
|
|
111
|
+
* @property {Object|null} errorPayload - Error payload from device response
|
|
112
|
+
* @property {string|null} deviceUuid - UUID of the device that returned the error
|
|
113
|
+
*/
|
|
114
|
+
class CommandError extends MerossError {
|
|
115
|
+
constructor(message, errorPayload = null, deviceUuid = null) {
|
|
116
|
+
super(message || 'Device command failed');
|
|
117
|
+
this.errorPayload = errorPayload;
|
|
118
|
+
this.deviceUuid = deviceUuid;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* MQTT connection/communication error.
|
|
124
|
+
*
|
|
125
|
+
* Thrown when MQTT connection or communication fails. Can occur due to network
|
|
126
|
+
* issues, MQTT broker unavailability, connection timeouts, or protocol errors
|
|
127
|
+
* during message transmission. Check network connectivity and MQTT broker status.
|
|
128
|
+
*
|
|
129
|
+
* @class
|
|
130
|
+
* @extends MerossError
|
|
131
|
+
* @property {string|null} topic - MQTT topic related to the error
|
|
132
|
+
* @property {Object|null} mqttMessage - MQTT message related to the error
|
|
133
|
+
*/
|
|
134
|
+
class MqttError extends MerossError {
|
|
135
|
+
constructor(message, topic = null, mqttMessage = null) {
|
|
136
|
+
super(message || 'MQTT error occurred');
|
|
137
|
+
this.topic = topic;
|
|
138
|
+
this.mqttMessage = mqttMessage;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Device not connected error.
|
|
144
|
+
*
|
|
145
|
+
* Thrown when attempting to send a command to a device that is not connected.
|
|
146
|
+
* Commands can only be sent after the device has established a connection and
|
|
147
|
+
* emitted the 'connected' event. Wait for the device to connect before sending commands.
|
|
148
|
+
*
|
|
149
|
+
* @class
|
|
150
|
+
* @extends MerossError
|
|
151
|
+
* @property {string|null} deviceUuid - UUID of the device that is not connected
|
|
152
|
+
*/
|
|
153
|
+
class UnconnectedError extends MerossError {
|
|
154
|
+
constructor(message, deviceUuid = null) {
|
|
155
|
+
super(message || 'Device is not connected');
|
|
156
|
+
this.deviceUuid = deviceUuid;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Unknown or unsupported device type error.
|
|
162
|
+
*
|
|
163
|
+
* Thrown when a device operation is attempted on a device type that doesn't
|
|
164
|
+
* support the requested feature or when the device type is not recognized.
|
|
165
|
+
* Indicates a mismatch between the requested operation and device capabilities.
|
|
166
|
+
* Verify the device type supports the requested operation.
|
|
167
|
+
*
|
|
168
|
+
* @class
|
|
169
|
+
* @extends MerossError
|
|
170
|
+
* @property {string|null} deviceType - Device type that is unsupported
|
|
171
|
+
*/
|
|
172
|
+
class UnknownDeviceTypeError extends MerossError {
|
|
173
|
+
constructor(message, deviceType = null) {
|
|
174
|
+
super(message || 'Unknown or unsupported device type');
|
|
175
|
+
this.deviceType = deviceType;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Handles token-related error codes.
|
|
181
|
+
*
|
|
182
|
+
* Maps token expiration and token limit error codes to appropriate error
|
|
183
|
+
* classes. Token errors indicate authentication token issues that require
|
|
184
|
+
* re-authentication or token management.
|
|
185
|
+
*
|
|
186
|
+
* @private
|
|
187
|
+
* @param {number} errorCode - The error code
|
|
188
|
+
* @param {string} message - Error message
|
|
189
|
+
* @returns {MerossError|null} Error instance or null if not a token error
|
|
190
|
+
*/
|
|
191
|
+
function _handleTokenErrors(errorCode, message) {
|
|
192
|
+
const { TokenExpiredError, TooManyTokensError } = require('./http/exception');
|
|
193
|
+
|
|
194
|
+
if (errorCode === 1019 || errorCode === 1022 || errorCode === 1200) {
|
|
195
|
+
return new TokenExpiredError(message, errorCode);
|
|
196
|
+
}
|
|
197
|
+
if (errorCode === 1301) {
|
|
198
|
+
return new TooManyTokensError(message);
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Handles authentication error codes (1000-1008).
|
|
205
|
+
*
|
|
206
|
+
* Maps authentication error codes to AuthenticationError instances. These
|
|
207
|
+
* errors indicate login failures due to invalid credentials or account issues.
|
|
208
|
+
*
|
|
209
|
+
* @private
|
|
210
|
+
* @param {number} errorCode - The error code
|
|
211
|
+
* @param {string} message - Error message
|
|
212
|
+
* @returns {MerossError|null} Error instance or null if not an auth error
|
|
213
|
+
*/
|
|
214
|
+
function _handleAuthenticationErrors(errorCode, message) {
|
|
215
|
+
if (errorCode >= 1000 && errorCode <= 1008) {
|
|
216
|
+
return new AuthenticationError(message, errorCode);
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Handles MFA-related error codes.
|
|
223
|
+
*
|
|
224
|
+
* Maps multi-factor authentication error codes to appropriate error classes.
|
|
225
|
+
* MFA errors indicate issues with two-factor authentication during login.
|
|
226
|
+
*
|
|
227
|
+
* @private
|
|
228
|
+
* @param {number} errorCode - The error code
|
|
229
|
+
* @param {string} message - Error message
|
|
230
|
+
* @returns {MerossError|null} Error instance or null if not an MFA error
|
|
231
|
+
*/
|
|
232
|
+
function _handleMFAErrors(errorCode, message) {
|
|
233
|
+
const { MFARequiredError, WrongMFAError } = require('./http/exception');
|
|
234
|
+
|
|
235
|
+
if (errorCode === 1032) {
|
|
236
|
+
return new WrongMFAError(message);
|
|
237
|
+
}
|
|
238
|
+
if (errorCode === 1033) {
|
|
239
|
+
return new MFARequiredError(message);
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Handles domain and API limit/access error codes.
|
|
246
|
+
*
|
|
247
|
+
* Maps domain configuration errors and API limit/access errors to appropriate
|
|
248
|
+
* error classes. Domain errors indicate incorrect API/MQTT domain configuration,
|
|
249
|
+
* while limit errors indicate rate limiting or permission issues.
|
|
250
|
+
*
|
|
251
|
+
* @private
|
|
252
|
+
* @param {number} errorCode - The error code
|
|
253
|
+
* @param {string} message - Error message
|
|
254
|
+
* @param {Object} context - Additional context
|
|
255
|
+
* @returns {MerossError|null} Error instance or null if not a domain/limit error
|
|
256
|
+
*/
|
|
257
|
+
function _handleDomainAndLimitErrors(errorCode, message, context) {
|
|
258
|
+
const { BadDomainError } = require('./http/exception');
|
|
259
|
+
|
|
260
|
+
if (errorCode === 1030) {
|
|
261
|
+
return new BadDomainError(message, context.apiDomain, context.mqttDomain);
|
|
262
|
+
}
|
|
263
|
+
if (errorCode === 1042) {
|
|
264
|
+
return new ApiLimitReachedError(message);
|
|
265
|
+
}
|
|
266
|
+
if (errorCode === 1043) {
|
|
267
|
+
return new ResourceAccessDeniedError(message);
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Handles HTTP status code-based errors.
|
|
274
|
+
*
|
|
275
|
+
* Maps HTTP status codes to appropriate error classes. HTTP errors indicate
|
|
276
|
+
* server-side issues or authentication problems at the HTTP protocol level.
|
|
277
|
+
*
|
|
278
|
+
* @private
|
|
279
|
+
* @param {number} httpStatusCode - HTTP status code
|
|
280
|
+
* @param {number} errorCode - The API error code
|
|
281
|
+
* @param {string} message - Error message
|
|
282
|
+
* @returns {MerossError|null} Error instance or null if not an HTTP error
|
|
283
|
+
*/
|
|
284
|
+
function _handleHttpStatusErrors(httpStatusCode, errorCode, message) {
|
|
285
|
+
const { HttpApiError, UnauthorizedError } = require('./http/exception');
|
|
286
|
+
|
|
287
|
+
if (httpStatusCode === 401) {
|
|
288
|
+
return new UnauthorizedError(message, errorCode, httpStatusCode);
|
|
289
|
+
}
|
|
290
|
+
if (httpStatusCode && httpStatusCode >= 400) {
|
|
291
|
+
return new HttpApiError(message, errorCode, httpStatusCode);
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Maps error codes to appropriate error classes.
|
|
298
|
+
*
|
|
299
|
+
* Converts API error codes into specific error class instances based on the
|
|
300
|
+
* error code value and context. Provides consistent error handling by mapping
|
|
301
|
+
* numeric codes to typed error objects with appropriate properties. This
|
|
302
|
+
* centralizes error handling logic and ensures all errors are properly typed.
|
|
303
|
+
*
|
|
304
|
+
* @param {number} errorCode - The error code from API response
|
|
305
|
+
* @param {Object} [context={}] - Additional context
|
|
306
|
+
* @param {string} [context.info] - Error message from API
|
|
307
|
+
* @param {string} [context.deviceUuid] - Device UUID (for device-related errors)
|
|
308
|
+
* @param {number} [context.httpStatusCode] - HTTP status code
|
|
309
|
+
* @param {string} [context.apiDomain] - API domain (for BadDomainError)
|
|
310
|
+
* @param {string} [context.mqttDomain] - MQTT domain (for BadDomainError)
|
|
311
|
+
* @returns {MerossError} Appropriate error instance
|
|
312
|
+
*/
|
|
313
|
+
function mapErrorCodeToError(errorCode, context = {}) {
|
|
314
|
+
const { info, httpStatusCode } = context;
|
|
315
|
+
const { getErrorMessage } = require('./http/error-codes');
|
|
316
|
+
const message = info || getErrorMessage(errorCode);
|
|
317
|
+
|
|
318
|
+
// Check token-related errors
|
|
319
|
+
const tokenError = _handleTokenErrors(errorCode, message);
|
|
320
|
+
if (tokenError) {
|
|
321
|
+
return tokenError;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check authentication errors
|
|
325
|
+
const authError = _handleAuthenticationErrors(errorCode, message);
|
|
326
|
+
if (authError) {
|
|
327
|
+
return authError;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check MFA errors
|
|
331
|
+
const mfaError = _handleMFAErrors(errorCode, message);
|
|
332
|
+
if (mfaError) {
|
|
333
|
+
return mfaError;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check domain and API limit errors
|
|
337
|
+
const domainError = _handleDomainAndLimitErrors(errorCode, message, context);
|
|
338
|
+
if (domainError) {
|
|
339
|
+
return domainError;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Check HTTP status code errors
|
|
343
|
+
const httpError = _handleHttpStatusErrors(httpStatusCode, errorCode, message);
|
|
344
|
+
if (httpError) {
|
|
345
|
+
return httpError;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Default: return generic MerossError
|
|
349
|
+
return new MerossError(`${errorCode} (${getErrorMessage(errorCode)})${info ? ` - ${info}` : ''}`, errorCode);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
module.exports = {
|
|
353
|
+
MerossError,
|
|
354
|
+
AuthenticationError,
|
|
355
|
+
ApiLimitReachedError,
|
|
356
|
+
ResourceAccessDeniedError,
|
|
357
|
+
CommandTimeoutError,
|
|
358
|
+
CommandError,
|
|
359
|
+
MqttError,
|
|
360
|
+
UnconnectedError,
|
|
361
|
+
UnknownDeviceTypeError,
|
|
362
|
+
mapErrorCodeToError
|
|
363
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { OnlineStatus } = require('../enums');
|
|
4
|
+
const { DEFAULT_MQTT_HOST, DEFAULT_MQTT_PORT } = require('../constants');
|
|
5
|
+
const { extractDomain, extractPort } = require('../../utilities/network');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents device information from the HTTP API
|
|
9
|
+
*
|
|
10
|
+
* This class encapsulates device metadata retrieved from the Meross HTTP API,
|
|
11
|
+
* including device identification, versions, channels, and network configuration.
|
|
12
|
+
* It provides helper methods for extracting MQTT connection information.
|
|
13
|
+
*
|
|
14
|
+
* @class
|
|
15
|
+
* @example
|
|
16
|
+
* // Use fromDict() to create instances
|
|
17
|
+
* const deviceInfo = HttpDeviceInfo.fromDict({
|
|
18
|
+
* uuid: '12345',
|
|
19
|
+
* dev_name: 'My Device',
|
|
20
|
+
* device_type: 'mss310',
|
|
21
|
+
* online_status: 1,
|
|
22
|
+
* channels: []
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
class HttpDeviceInfo {
|
|
26
|
+
/**
|
|
27
|
+
* Creates an HttpDeviceInfo instance from a dictionary object.
|
|
28
|
+
* Normalizes incoming keys (handles camelCase and snake_case) to camelCase.
|
|
29
|
+
* This is the only way to create instances.
|
|
30
|
+
*
|
|
31
|
+
* @static
|
|
32
|
+
* @param {Object} jsonDict - Raw API response object with any key format
|
|
33
|
+
* @param {string} [jsonDict.uuid] - Device UUID
|
|
34
|
+
* @param {string} [jsonDict.devName] - Device name (camelCase)
|
|
35
|
+
* @param {string} [jsonDict.dev_name] - Device name (snake_case)
|
|
36
|
+
* @param {string} [jsonDict.deviceType] - Device type (camelCase)
|
|
37
|
+
* @param {string} [jsonDict.device_type] - Device type (snake_case)
|
|
38
|
+
* @param {Array<Object>} [jsonDict.channels] - Raw channels array from API
|
|
39
|
+
* @param {string} [jsonDict.fmwareVersion] - Firmware version (camelCase)
|
|
40
|
+
* @param {string} [jsonDict.fmware_version] - Firmware version (snake_case)
|
|
41
|
+
* @param {string} [jsonDict.hdwareVersion] - Hardware version (camelCase)
|
|
42
|
+
* @param {string} [jsonDict.hdware_version] - Hardware version (snake_case)
|
|
43
|
+
* @param {string} [jsonDict.domain] - MQTT domain
|
|
44
|
+
* @param {string} [jsonDict.reservedDomain] - Reserved MQTT domain (camelCase)
|
|
45
|
+
* @param {string} [jsonDict.reserved_domain] - Reserved MQTT domain (snake_case)
|
|
46
|
+
* @param {string} [jsonDict.subType] - Device sub-type (camelCase)
|
|
47
|
+
* @param {string} [jsonDict.sub_type] - Device sub-type (snake_case)
|
|
48
|
+
* @param {number|Date|string} [jsonDict.bindTime] - Device bind timestamp (camelCase)
|
|
49
|
+
* @param {number|Date|string} [jsonDict.bind_time] - Device bind timestamp (snake_case)
|
|
50
|
+
* @param {string} [jsonDict.skillNumber] - Skill number (camelCase)
|
|
51
|
+
* @param {string} [jsonDict.skill_number] - Skill number (snake_case)
|
|
52
|
+
* @param {string} [jsonDict.userDevIcon] - User device icon (camelCase)
|
|
53
|
+
* @param {string} [jsonDict.user_dev_icon] - User device icon (snake_case)
|
|
54
|
+
* @param {number} [jsonDict.iconType] - Icon type (camelCase)
|
|
55
|
+
* @param {number} [jsonDict.icon_type] - Icon type (snake_case)
|
|
56
|
+
* @param {string} [jsonDict.region] - Device region
|
|
57
|
+
* @param {string} [jsonDict.devIconId] - Device icon ID (camelCase)
|
|
58
|
+
* @param {string} [jsonDict.dev_icon_id] - Device icon ID (snake_case)
|
|
59
|
+
* @param {number} [jsonDict.onlineStatus] - Online status (camelCase)
|
|
60
|
+
* @param {number} [jsonDict.online_status] - Online status (snake_case)
|
|
61
|
+
* @returns {HttpDeviceInfo} New HttpDeviceInfo instance
|
|
62
|
+
*/
|
|
63
|
+
static fromDict(jsonDict) {
|
|
64
|
+
if (!jsonDict || typeof jsonDict !== 'object') {
|
|
65
|
+
throw new Error('Device info dictionary is required');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The Meross API returns properties in inconsistent formats (camelCase vs snake_case).
|
|
69
|
+
// This mapping allows us to accept either format and normalize to camelCase for internal use.
|
|
70
|
+
const propertyMappings = {
|
|
71
|
+
uuid: ['uuid'],
|
|
72
|
+
devName: ['devName', 'dev_name'],
|
|
73
|
+
deviceType: ['deviceType', 'device_type'],
|
|
74
|
+
channels: ['channels'],
|
|
75
|
+
fmwareVersion: ['fmwareVersion', 'fmware_version'],
|
|
76
|
+
hdwareVersion: ['hdwareVersion', 'hdware_version'],
|
|
77
|
+
domain: ['domain'],
|
|
78
|
+
reservedDomain: ['reservedDomain', 'reserved_domain'],
|
|
79
|
+
subType: ['subType', 'sub_type'],
|
|
80
|
+
bindTime: ['bindTime', 'bind_time'],
|
|
81
|
+
skillNumber: ['skillNumber', 'skill_number'],
|
|
82
|
+
userDevIcon: ['userDevIcon', 'user_dev_icon'],
|
|
83
|
+
iconType: ['iconType', 'icon_type'],
|
|
84
|
+
region: ['region'],
|
|
85
|
+
devIconId: ['devIconId', 'dev_icon_id'],
|
|
86
|
+
onlineStatus: ['onlineStatus', 'online_status']
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Normalize properties by checking alternatives in priority order (camelCase first, then snake_case).
|
|
90
|
+
// This ensures consistent property names regardless of API response format.
|
|
91
|
+
const normalized = {};
|
|
92
|
+
for (const [camelKey, alternatives] of Object.entries(propertyMappings)) {
|
|
93
|
+
for (const key of alternatives) {
|
|
94
|
+
if (jsonDict[key] !== null && jsonDict[key] !== undefined) {
|
|
95
|
+
normalized[camelKey] = jsonDict[key];
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Use Object.create() instead of a constructor to allow static factory pattern.
|
|
102
|
+
// This prevents accidental instantiation via 'new' and enforces use of fromDict().
|
|
103
|
+
const instance = Object.create(HttpDeviceInfo.prototype);
|
|
104
|
+
instance.uuid = normalized.uuid;
|
|
105
|
+
instance.devName = normalized.devName;
|
|
106
|
+
instance.deviceType = normalized.deviceType;
|
|
107
|
+
instance.channels = normalized.channels || [];
|
|
108
|
+
instance.fmwareVersion = normalized.fmwareVersion;
|
|
109
|
+
instance.hdwareVersion = normalized.hdwareVersion;
|
|
110
|
+
instance.domain = normalized.domain;
|
|
111
|
+
instance.reservedDomain = normalized.reservedDomain;
|
|
112
|
+
instance.subType = normalized.subType;
|
|
113
|
+
instance.skillNumber = normalized.skillNumber;
|
|
114
|
+
instance.userDevIcon = normalized.userDevIcon;
|
|
115
|
+
instance.iconType = normalized.iconType;
|
|
116
|
+
instance.region = normalized.region;
|
|
117
|
+
instance.devIconId = normalized.devIconId;
|
|
118
|
+
|
|
119
|
+
// Convert onlineStatus to a number, defaulting to UNKNOWN if invalid or missing.
|
|
120
|
+
// The API may return non-numeric values or omit the field entirely.
|
|
121
|
+
if (normalized.onlineStatus !== undefined && normalized.onlineStatus !== null) {
|
|
122
|
+
if (typeof normalized.onlineStatus === 'number') {
|
|
123
|
+
instance.onlineStatus = normalized.onlineStatus;
|
|
124
|
+
} else {
|
|
125
|
+
instance.onlineStatus = OnlineStatus.UNKNOWN;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
instance.onlineStatus = OnlineStatus.UNKNOWN;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Convert bindTime from Unix timestamp (seconds) to Date object.
|
|
132
|
+
// The API returns timestamps as numbers, but we store them as Date instances for easier manipulation.
|
|
133
|
+
if (normalized.bindTime !== undefined && normalized.bindTime !== null) {
|
|
134
|
+
if (typeof normalized.bindTime === 'number') {
|
|
135
|
+
instance.bindTime = new Date(normalized.bindTime * 1000);
|
|
136
|
+
} else if (normalized.bindTime instanceof Date) {
|
|
137
|
+
instance.bindTime = normalized.bindTime;
|
|
138
|
+
} else if (typeof normalized.bindTime === 'string') {
|
|
139
|
+
instance.bindTime = new Date(normalized.bindTime);
|
|
140
|
+
} else {
|
|
141
|
+
instance.bindTime = null;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
instance.bindTime = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return instance;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Converts the instance to a plain object dictionary with camelCase keys
|
|
152
|
+
*
|
|
153
|
+
* @returns {Object} Plain object with camelCase property keys
|
|
154
|
+
*/
|
|
155
|
+
toDict() {
|
|
156
|
+
return {
|
|
157
|
+
uuid: this.uuid,
|
|
158
|
+
devName: this.devName,
|
|
159
|
+
deviceType: this.deviceType,
|
|
160
|
+
channels: this.channels,
|
|
161
|
+
fmwareVersion: this.fmwareVersion,
|
|
162
|
+
hdwareVersion: this.hdwareVersion,
|
|
163
|
+
domain: this.domain,
|
|
164
|
+
reservedDomain: this.reservedDomain,
|
|
165
|
+
subType: this.subType,
|
|
166
|
+
bindTime: this.bindTime,
|
|
167
|
+
skillNumber: this.skillNumber,
|
|
168
|
+
userDevIcon: this.userDevIcon,
|
|
169
|
+
iconType: this.iconType,
|
|
170
|
+
region: this.region,
|
|
171
|
+
devIconId: this.devIconId,
|
|
172
|
+
onlineStatus: this.onlineStatus
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Gets the MQTT server hostname for this device
|
|
178
|
+
*
|
|
179
|
+
* Extracts the hostname from the domain or reservedDomain field.
|
|
180
|
+
* The domain field is preferred as it represents the active MQTT endpoint,
|
|
181
|
+
* while reservedDomain is a fallback. Defaults to a standard hostname if neither is available.
|
|
182
|
+
*
|
|
183
|
+
* @returns {string} MQTT hostname
|
|
184
|
+
*/
|
|
185
|
+
getMqttHost() {
|
|
186
|
+
if (this.domain) {
|
|
187
|
+
return extractDomain(this.domain);
|
|
188
|
+
}
|
|
189
|
+
if (this.reservedDomain) {
|
|
190
|
+
return extractDomain(this.reservedDomain);
|
|
191
|
+
}
|
|
192
|
+
return DEFAULT_MQTT_HOST;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Gets the MQTT server port for this device
|
|
197
|
+
*
|
|
198
|
+
* Extracts the port from the domain or reservedDomain field.
|
|
199
|
+
* The domain field is preferred as it represents the active MQTT endpoint,
|
|
200
|
+
* while reservedDomain is a fallback. Defaults to a standard port if neither is available.
|
|
201
|
+
*
|
|
202
|
+
* @returns {number} MQTT port number
|
|
203
|
+
*/
|
|
204
|
+
getMqttPort() {
|
|
205
|
+
if (this.domain) {
|
|
206
|
+
return extractPort(this.domain, DEFAULT_MQTT_PORT);
|
|
207
|
+
}
|
|
208
|
+
if (this.reservedDomain) {
|
|
209
|
+
return extractPort(this.reservedDomain, DEFAULT_MQTT_PORT);
|
|
210
|
+
}
|
|
211
|
+
return DEFAULT_MQTT_PORT;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = HttpDeviceInfo;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error code to message mapping
|
|
5
|
+
*
|
|
6
|
+
* Maps Meross API error codes to human-readable error messages. These messages
|
|
7
|
+
* are provided by the Meross API and used as default error messages when the
|
|
8
|
+
* API doesn't provide a custom message.
|
|
9
|
+
*
|
|
10
|
+
* @module errorcodes
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const errorMessages = {
|
|
14
|
+
500: 'The selected timezone is not supported',
|
|
15
|
+
1000: 'Wrong or missing user',
|
|
16
|
+
1001: 'Wrong or missing password',
|
|
17
|
+
1002: 'Account does not exist',
|
|
18
|
+
1003: 'This account has been disabled or deleted',
|
|
19
|
+
1004: 'Wrong email or password',
|
|
20
|
+
1005: 'Invalid email address',
|
|
21
|
+
1006: 'Bad password format',
|
|
22
|
+
1007: 'User already exists',
|
|
23
|
+
1008: 'This email is not registered',
|
|
24
|
+
1009: 'Email send failed',
|
|
25
|
+
1011: 'Wrong Ticket',
|
|
26
|
+
1012: 'Code Overdue',
|
|
27
|
+
1013: 'Wrong Code',
|
|
28
|
+
1014: 'Duplicate password',
|
|
29
|
+
1015: 'Same email when changing account email',
|
|
30
|
+
1019: 'Token expired',
|
|
31
|
+
1021: 'Unknown error',
|
|
32
|
+
1022: 'Token error',
|
|
33
|
+
1023: 'Unknown error',
|
|
34
|
+
1024: 'Unknown error',
|
|
35
|
+
1025: 'Unknown error',
|
|
36
|
+
1026: 'Unknown error',
|
|
37
|
+
1027: 'Unknown error',
|
|
38
|
+
1028: 'Requested too frequently',
|
|
39
|
+
1030: 'Redirect app to login other than this region',
|
|
40
|
+
1031: 'Username does not match',
|
|
41
|
+
1032: 'Invalid MFA code. Please use a current MFA code.',
|
|
42
|
+
1033: 'MFA is activated for the account but MFA code not provided. Please provide a current MFA code.',
|
|
43
|
+
1035: 'Operation is locked',
|
|
44
|
+
1041: 'Repeat checkin',
|
|
45
|
+
1042: 'API Top limit reached',
|
|
46
|
+
1043: 'Resource access deny',
|
|
47
|
+
1200: 'Token has expired',
|
|
48
|
+
1201: 'Server was unable to generate token',
|
|
49
|
+
1202: 'Unknown error',
|
|
50
|
+
1203: 'Unknown error',
|
|
51
|
+
1204: 'Unknown error',
|
|
52
|
+
1210: 'Unknown error',
|
|
53
|
+
1211: 'Unknown error',
|
|
54
|
+
1212: 'Unknown error',
|
|
55
|
+
1213: 'Unknown error',
|
|
56
|
+
1214: 'Unknown error',
|
|
57
|
+
1215: 'Unknown error',
|
|
58
|
+
1226: 'Unknown error',
|
|
59
|
+
1227: 'Unknown error',
|
|
60
|
+
1228: 'Unknown error',
|
|
61
|
+
1229: 'Unknown error',
|
|
62
|
+
1230: 'Unknown error',
|
|
63
|
+
1231: 'Unknown error',
|
|
64
|
+
1232: 'Unknown error',
|
|
65
|
+
1233: 'Unknown error',
|
|
66
|
+
1255: 'The number of remote control boards exceeded the limit',
|
|
67
|
+
1256: 'Compatible mode having',
|
|
68
|
+
1257: 'Compatible mode not having',
|
|
69
|
+
1301: 'You have issued too many tokens without logging out and your account might have been temporarily disabled.',
|
|
70
|
+
1400: 'Unknown error',
|
|
71
|
+
1401: 'Unknown error',
|
|
72
|
+
1402: 'Unknown error',
|
|
73
|
+
1403: 'Unknown error',
|
|
74
|
+
1500: 'Unknown error',
|
|
75
|
+
1501: 'Unknown error',
|
|
76
|
+
1502: 'Unknown error',
|
|
77
|
+
1503: 'Unknown error',
|
|
78
|
+
1504: 'Unknown error',
|
|
79
|
+
1601: 'Unknown error',
|
|
80
|
+
1602: 'Unknown error',
|
|
81
|
+
1603: 'Unknown error',
|
|
82
|
+
1604: 'Unknown error',
|
|
83
|
+
1605: 'Unknown error',
|
|
84
|
+
1700: 'Unknown error',
|
|
85
|
+
5000: 'Unknown or generic error',
|
|
86
|
+
5001: 'Unknown or generic error',
|
|
87
|
+
5002: 'Unknown or generic error',
|
|
88
|
+
5003: 'Unknown or generic error',
|
|
89
|
+
5004: 'Unknown or generic error',
|
|
90
|
+
5020: 'Infrared Remote device is busy',
|
|
91
|
+
5021: 'Infrared record timeout',
|
|
92
|
+
5022: 'Infrared record invalid',
|
|
93
|
+
10001: 'System error',
|
|
94
|
+
10002: 'Unknown error',
|
|
95
|
+
10003: 'Serialize error',
|
|
96
|
+
10006: 'Http common error',
|
|
97
|
+
20101: 'Invalid parameter',
|
|
98
|
+
20106: 'Not existing resource',
|
|
99
|
+
20112: 'Unsupported',
|
|
100
|
+
20115: 'Send email limit'
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Gets the error message for a given error code
|
|
105
|
+
*
|
|
106
|
+
* Maps Meross API error codes to human-readable messages. These messages are
|
|
107
|
+
* standardized by the Meross API documentation and provide context when the API
|
|
108
|
+
* response doesn't include a custom error message. Returns a generic message for
|
|
109
|
+
* unmapped codes to ensure callers always receive a meaningful error description.
|
|
110
|
+
*
|
|
111
|
+
* @param {number} code - The error code to look up
|
|
112
|
+
* @returns {string} The error message for the code, or 'Unknown error' if not found
|
|
113
|
+
*/
|
|
114
|
+
function getErrorMessage(code) {
|
|
115
|
+
return errorMessages[code] || 'Unknown error';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
getErrorMessage
|
|
120
|
+
};
|
|
121
|
+
|