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,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { MerossError, AuthenticationError } = require('../exception');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base class for HTTP API errors
|
|
7
|
+
*
|
|
8
|
+
* Extends MerossError with HTTP status code information to distinguish between
|
|
9
|
+
* different types of HTTP failures (client errors, server errors, etc.).
|
|
10
|
+
*
|
|
11
|
+
* @class
|
|
12
|
+
* @extends MerossError
|
|
13
|
+
* @property {number|null} httpStatusCode - HTTP status code (e.g., 400, 500)
|
|
14
|
+
*/
|
|
15
|
+
class HttpApiError extends MerossError {
|
|
16
|
+
constructor(message, errorCode = null, httpStatusCode = null) {
|
|
17
|
+
super(message || 'HTTP API error occurred', errorCode);
|
|
18
|
+
this.httpStatusCode = httpStatusCode;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Unauthorized access error (HTTP 401 or similar)
|
|
24
|
+
*
|
|
25
|
+
* Thrown when an API request is made without proper authentication or with invalid
|
|
26
|
+
* credentials. Typically indicates token expiration or an invalid token that
|
|
27
|
+
* cannot be used for authorization.
|
|
28
|
+
*
|
|
29
|
+
* @class
|
|
30
|
+
* @extends HttpApiError
|
|
31
|
+
* @property {number} httpStatusCode - HTTP status code (typically 401)
|
|
32
|
+
*/
|
|
33
|
+
class UnauthorizedError extends HttpApiError {
|
|
34
|
+
constructor(message, errorCode = null, httpStatusCode = 401) {
|
|
35
|
+
super(message || 'Unauthorized access', errorCode, httpStatusCode);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Bad domain / region redirect error (error code 1030)
|
|
41
|
+
*
|
|
42
|
+
* Thrown when Meross API redirects to a different regional domain. This occurs
|
|
43
|
+
* when the account is registered in a different region than the one being used
|
|
44
|
+
* for API requests. The error response includes the correct regional endpoints
|
|
45
|
+
* that should be used for subsequent requests.
|
|
46
|
+
*
|
|
47
|
+
* @class
|
|
48
|
+
* @extends MerossError
|
|
49
|
+
* @property {number} errorCode - Always 1030
|
|
50
|
+
* @property {string|null} apiDomain - The correct API domain for this account
|
|
51
|
+
* @property {string|null} mqttDomain - The correct MQTT domain for this account
|
|
52
|
+
*/
|
|
53
|
+
class BadDomainError extends MerossError {
|
|
54
|
+
constructor(message, apiDomain = null, mqttDomain = null) {
|
|
55
|
+
super(message || 'Redirect app to login other than this region', 1030);
|
|
56
|
+
this.apiDomain = apiDomain;
|
|
57
|
+
this.mqttDomain = mqttDomain;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Token expired or invalid errors (error codes 1019, 1022, 1200)
|
|
63
|
+
*
|
|
64
|
+
* Thrown when the authentication token has expired or is invalid. Tokens
|
|
65
|
+
* expire after a period of inactivity or when explicitly invalidated by the
|
|
66
|
+
* authentication service.
|
|
67
|
+
*
|
|
68
|
+
* @class
|
|
69
|
+
* @extends MerossError
|
|
70
|
+
* @property {number} errorCode - API error code (1019, 1022, or 1200)
|
|
71
|
+
*/
|
|
72
|
+
class TokenExpiredError extends MerossError {
|
|
73
|
+
constructor(message, errorCode) {
|
|
74
|
+
super(message || 'Token has expired or is invalid', errorCode);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Too many tokens error (error code 1301)
|
|
80
|
+
*
|
|
81
|
+
* Thrown when too many authentication tokens have been issued without logging out.
|
|
82
|
+
* Meross enforces a limit on concurrent sessions per account, and exceeding this
|
|
83
|
+
* limit results in account-level restrictions until tokens are revoked.
|
|
84
|
+
*
|
|
85
|
+
* @class
|
|
86
|
+
* @extends MerossError
|
|
87
|
+
* @property {number} errorCode - Always 1301
|
|
88
|
+
*/
|
|
89
|
+
class TooManyTokensError extends MerossError {
|
|
90
|
+
constructor(message) {
|
|
91
|
+
super(message || 'You have issued too many tokens without logging out and your account might have been temporarily disabled.', 1301);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* MFA code required error (error code 1033)
|
|
97
|
+
*
|
|
98
|
+
* Thrown when MFA (Multi-Factor Authentication) is enabled for the account but
|
|
99
|
+
* no MFA code was provided during login. The login process requires an additional
|
|
100
|
+
* authentication step that was not completed.
|
|
101
|
+
*
|
|
102
|
+
* @class
|
|
103
|
+
* @extends AuthenticationError
|
|
104
|
+
* @property {number} errorCode - Always 1033
|
|
105
|
+
*/
|
|
106
|
+
class MFARequiredError extends AuthenticationError {
|
|
107
|
+
constructor(message) {
|
|
108
|
+
super(message || 'MFA is activated for the account but MFA code not provided. Please provide a current MFA code.', 1033);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Alias for MFARequiredError
|
|
114
|
+
*
|
|
115
|
+
* Provided for backward compatibility and clearer naming in contexts where
|
|
116
|
+
* the error represents a missing MFA code rather than a general MFA requirement.
|
|
117
|
+
*
|
|
118
|
+
* @class
|
|
119
|
+
* @extends MFARequiredError
|
|
120
|
+
*/
|
|
121
|
+
class MissingMFAError extends MFARequiredError {
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Wrong MFA code error (error code 1032)
|
|
126
|
+
*
|
|
127
|
+
* Thrown when an invalid or expired MFA code is provided during login.
|
|
128
|
+
* The code provided does not match the current valid code from the
|
|
129
|
+
* authenticator application.
|
|
130
|
+
*
|
|
131
|
+
* @class
|
|
132
|
+
* @extends AuthenticationError
|
|
133
|
+
* @property {number} errorCode - Always 1032
|
|
134
|
+
*/
|
|
135
|
+
class WrongMFAError extends AuthenticationError {
|
|
136
|
+
constructor(message) {
|
|
137
|
+
super(message || 'Invalid MFA code. Please use a current MFA code.', 1032);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
HttpApiError,
|
|
143
|
+
UnauthorizedError,
|
|
144
|
+
BadDomainError,
|
|
145
|
+
TokenExpiredError,
|
|
146
|
+
TooManyTokensError,
|
|
147
|
+
MFARequiredError,
|
|
148
|
+
MissingMFAError,
|
|
149
|
+
WrongMFAError
|
|
150
|
+
};
|
|
151
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents subdevice information from the HTTP API
|
|
5
|
+
*
|
|
6
|
+
* This class encapsulates subdevice metadata retrieved from the Meross HTTP API,
|
|
7
|
+
* including subdevice identification, type, vendor, and icon information.
|
|
8
|
+
*
|
|
9
|
+
* @class
|
|
10
|
+
* @example
|
|
11
|
+
* // Use fromDict() to create instances
|
|
12
|
+
* const subdeviceInfo = HttpSubdeviceInfo.fromDict({
|
|
13
|
+
* sub_device_id: '12345',
|
|
14
|
+
* true_id: '67890',
|
|
15
|
+
* sub_device_type: 'ms130',
|
|
16
|
+
* sub_device_vendor: 'meross',
|
|
17
|
+
* sub_device_name: 'Temperature Sensor',
|
|
18
|
+
* sub_device_icon_id: 'icon123'
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
class HttpSubdeviceInfo {
|
|
22
|
+
/**
|
|
23
|
+
* Creates an HttpSubdeviceInfo instance from a dictionary object.
|
|
24
|
+
* Normalizes incoming keys (handles camelCase, snake_case, and generic keys) to camelCase.
|
|
25
|
+
* This is the only way to create instances.
|
|
26
|
+
*
|
|
27
|
+
* @static
|
|
28
|
+
* @param {Object} jsonDict - Raw API response object with any key format
|
|
29
|
+
* @param {string} [jsonDict.subDeviceId] - Subdevice ID (camelCase)
|
|
30
|
+
* @param {string} [jsonDict.sub_device_id] - Subdevice ID (snake_case)
|
|
31
|
+
* @param {string} [jsonDict.id] - Generic ID key (mapped to subDeviceId)
|
|
32
|
+
* @param {string} [jsonDict.trueId] - True ID (camelCase)
|
|
33
|
+
* @param {string} [jsonDict.true_id] - True ID (snake_case)
|
|
34
|
+
* @param {string} [jsonDict.subDeviceType] - Subdevice type (camelCase)
|
|
35
|
+
* @param {string} [jsonDict.sub_device_type] - Subdevice type (snake_case)
|
|
36
|
+
* @param {string} [jsonDict.type] - Generic type key (mapped to subDeviceType)
|
|
37
|
+
* @param {string} [jsonDict.subDeviceVendor] - Subdevice vendor (camelCase)
|
|
38
|
+
* @param {string} [jsonDict.sub_device_vendor] - Subdevice vendor (snake_case)
|
|
39
|
+
* @param {string} [jsonDict.subDeviceName] - Subdevice name (camelCase)
|
|
40
|
+
* @param {string} [jsonDict.sub_device_name] - Subdevice name (snake_case)
|
|
41
|
+
* @param {string} [jsonDict.name] - Generic name key (mapped to subDeviceName)
|
|
42
|
+
* @param {string} [jsonDict.subDeviceIconId] - Subdevice icon ID (camelCase)
|
|
43
|
+
* @param {string} [jsonDict.sub_device_icon_id] - Subdevice icon ID (snake_case)
|
|
44
|
+
* @returns {HttpSubdeviceInfo} New HttpSubdeviceInfo instance
|
|
45
|
+
*/
|
|
46
|
+
static fromDict(jsonDict) {
|
|
47
|
+
if (!jsonDict || typeof jsonDict !== 'object') {
|
|
48
|
+
throw new Error('Subdevice info dictionary is required');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The Meross API returns properties in inconsistent formats (camelCase, snake_case, or generic keys).
|
|
52
|
+
// This mapping allows us to accept any format and normalize to camelCase for internal use.
|
|
53
|
+
// Generic keys like 'id', 'type', and 'name' are included as fallbacks for API variations.
|
|
54
|
+
const propertyMappings = {
|
|
55
|
+
subDeviceId: ['subDeviceId', 'sub_device_id', 'id'],
|
|
56
|
+
trueId: ['trueId', 'true_id'],
|
|
57
|
+
subDeviceType: ['subDeviceType', 'sub_device_type', 'type'],
|
|
58
|
+
subDeviceVendor: ['subDeviceVendor', 'sub_device_vendor'],
|
|
59
|
+
subDeviceName: ['subDeviceName', 'sub_device_name', 'name'],
|
|
60
|
+
subDeviceIconId: ['subDeviceIconId', 'sub_device_icon_id']
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Normalize properties by checking alternatives in priority order (camelCase, snake_case, then generic).
|
|
64
|
+
// This ensures consistent property names regardless of API response format.
|
|
65
|
+
const normalized = {};
|
|
66
|
+
for (const [camelKey, alternatives] of Object.entries(propertyMappings)) {
|
|
67
|
+
for (const key of alternatives) {
|
|
68
|
+
if (jsonDict[key] !== null && jsonDict[key] !== undefined) {
|
|
69
|
+
normalized[camelKey] = jsonDict[key];
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Use Object.create() instead of a constructor to allow static factory pattern.
|
|
76
|
+
// This prevents accidental instantiation via 'new' and enforces use of fromDict().
|
|
77
|
+
const instance = Object.create(HttpSubdeviceInfo.prototype);
|
|
78
|
+
instance.subDeviceId = normalized.subDeviceId || null;
|
|
79
|
+
instance.trueId = normalized.trueId || null;
|
|
80
|
+
instance.subDeviceType = normalized.subDeviceType || null;
|
|
81
|
+
instance.subDeviceVendor = normalized.subDeviceVendor || null;
|
|
82
|
+
instance.subDeviceName = normalized.subDeviceName || null;
|
|
83
|
+
instance.subDeviceIconId = normalized.subDeviceIconId || null;
|
|
84
|
+
|
|
85
|
+
return instance;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Converts the instance to a plain object dictionary with camelCase keys
|
|
90
|
+
*
|
|
91
|
+
* @returns {Object} Plain object with camelCase property keys
|
|
92
|
+
*/
|
|
93
|
+
toDict() {
|
|
94
|
+
return {
|
|
95
|
+
subDeviceId: this.subDeviceId,
|
|
96
|
+
trueId: this.trueId,
|
|
97
|
+
subDeviceType: this.subDeviceType,
|
|
98
|
+
subDeviceVendor: this.subDeviceVendor,
|
|
99
|
+
subDeviceName: this.subDeviceName,
|
|
100
|
+
subDeviceIconId: this.subDeviceIconId
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Returns a string representation of the subdevice info
|
|
106
|
+
*
|
|
107
|
+
* Provides a human-readable format for logging and debugging. Includes
|
|
108
|
+
* name, type, and both device IDs to uniquely identify the subdevice.
|
|
109
|
+
*
|
|
110
|
+
* @returns {string} String representation in the format "Name (type, ID x, TRUE-ID y)"
|
|
111
|
+
*/
|
|
112
|
+
toString() {
|
|
113
|
+
const name = this.subDeviceName || 'Unknown';
|
|
114
|
+
const type = this.subDeviceType || 'unknown';
|
|
115
|
+
const id = this.subDeviceId || 'N/A';
|
|
116
|
+
const trueId = this.trueId || 'N/A';
|
|
117
|
+
return `${name} (${type}, ID ${id}, TRUE-ID ${trueId})`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns a JSON representation of the subdevice info
|
|
122
|
+
*
|
|
123
|
+
* Serializes the subdevice data to JSON format. Uses toDict() to ensure
|
|
124
|
+
* consistent camelCase property names in the output.
|
|
125
|
+
*
|
|
126
|
+
* @returns {string} JSON string representation of the subdevice data
|
|
127
|
+
*/
|
|
128
|
+
toJSON() {
|
|
129
|
+
return JSON.stringify(this.toDict());
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = HttpSubdeviceInfo;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const GenericPushNotification = require('./generic');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Push notification for device alarm events.
|
|
7
|
+
*
|
|
8
|
+
* Handles alarm notifications from both standalone devices and hub subdevices.
|
|
9
|
+
* The alarm data structure varies: standalone devices send alarm data directly,
|
|
10
|
+
* while hub sensors include interconnection data with source subdevice information.
|
|
11
|
+
*
|
|
12
|
+
* @class
|
|
13
|
+
* @extends GenericPushNotification
|
|
14
|
+
* @example
|
|
15
|
+
* device.on('pushNotification', (notification) => {
|
|
16
|
+
* if (notification instanceof AlarmPushNotification) {
|
|
17
|
+
* console.log('Alarm triggered on channel:', notification.channel);
|
|
18
|
+
* console.log('Alarm value:', notification.value);
|
|
19
|
+
* console.log('Timestamp:', notification.timestamp);
|
|
20
|
+
* if (notification.subdevice_id) {
|
|
21
|
+
* console.log('From subdevice:', notification.subdevice_id);
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* });
|
|
25
|
+
*/
|
|
26
|
+
class AlarmPushNotification extends GenericPushNotification {
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new AlarmPushNotification instance.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} originatingDeviceUuid - UUID of the device that sent the notification
|
|
31
|
+
* @param {Object} rawData - Raw notification data from the device
|
|
32
|
+
* @param {Object|Array} [rawData.alarm] - Alarm event data (single object or array)
|
|
33
|
+
* @param {number} [rawData.alarm.channel] - Channel number where alarm occurred
|
|
34
|
+
* @param {Object} [rawData.alarm.event] - Alarm event details
|
|
35
|
+
* @param {Object} [rawData.alarm.event.interConn] - Interconnection data
|
|
36
|
+
* @param {*} [rawData.alarm.event.interConn.value] - Alarm value
|
|
37
|
+
* @param {number} [rawData.alarm.event.interConn.timestamp] - Alarm timestamp
|
|
38
|
+
* @param {Object|Array} [rawData.alarm.event.interConn.source] - Source device data
|
|
39
|
+
* @param {string|number} [rawData.alarm.event.interConn.source.subId] - Subdevice ID if from hub sensor
|
|
40
|
+
*/
|
|
41
|
+
constructor(originatingDeviceUuid, rawData) {
|
|
42
|
+
super('Appliance.Control.Alarm', originatingDeviceUuid, rawData);
|
|
43
|
+
|
|
44
|
+
// Devices may send single objects or arrays; normalize to array for consistent processing
|
|
45
|
+
const alarmRaw = rawData?.alarm;
|
|
46
|
+
const alarm = GenericPushNotification.normalizeToArray(alarmRaw);
|
|
47
|
+
|
|
48
|
+
// Update rawData so routing logic receives normalized structure
|
|
49
|
+
if (rawData && alarmRaw !== alarm) {
|
|
50
|
+
rawData.alarm = alarm;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (alarm && alarm.length > 0) {
|
|
54
|
+
const alarmEvent = alarm[0];
|
|
55
|
+
this._channel = alarmEvent?.channel;
|
|
56
|
+
|
|
57
|
+
const interConn = alarmEvent?.event?.interConn;
|
|
58
|
+
if (interConn) {
|
|
59
|
+
this._value = interConn.value;
|
|
60
|
+
this._timestamp = interConn.timestamp;
|
|
61
|
+
|
|
62
|
+
const { source } = interConn;
|
|
63
|
+
const sourceArray = GenericPushNotification.normalizeToArray(source);
|
|
64
|
+
if (sourceArray && sourceArray.length > 0) {
|
|
65
|
+
this._subDeviceId = sourceArray[0]?.subId;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Gets the alarm value.
|
|
73
|
+
*
|
|
74
|
+
* @returns {*} Alarm value or undefined if not available
|
|
75
|
+
*/
|
|
76
|
+
get value() {
|
|
77
|
+
return this._value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets the alarm timestamp.
|
|
82
|
+
*
|
|
83
|
+
* @returns {number|undefined} Timestamp when alarm occurred or undefined if not available
|
|
84
|
+
*/
|
|
85
|
+
get timestamp() {
|
|
86
|
+
return this._timestamp;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Gets the channel number where the alarm occurred.
|
|
91
|
+
*
|
|
92
|
+
* @returns {number|undefined} Channel number or undefined if not available
|
|
93
|
+
*/
|
|
94
|
+
get channel() {
|
|
95
|
+
return this._channel;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Gets the subdevice ID if alarm originated from a hub subdevice.
|
|
100
|
+
*
|
|
101
|
+
* Only present when the alarm comes from a hub sensor subdevice, not standalone devices.
|
|
102
|
+
*
|
|
103
|
+
* @returns {string|number|undefined} Subdevice ID or undefined if not from a subdevice
|
|
104
|
+
*/
|
|
105
|
+
// eslint-disable-next-line camelcase
|
|
106
|
+
get subdevice_id() {
|
|
107
|
+
return this._subDeviceId;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = AlarmPushNotification;
|
|
112
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const GenericPushNotification = require('./generic');
|
|
4
|
+
const { HardwareInfo, FirmwareInfo, TimeInfo } = require('./common');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Push notification for device binding events.
|
|
8
|
+
*
|
|
9
|
+
* Emitted when a device is bound to a user account or when binding information is updated.
|
|
10
|
+
* The binding data includes hardware and firmware metadata that uniquely identifies the device
|
|
11
|
+
* and its capabilities, which is used for device registration and feature detection.
|
|
12
|
+
*
|
|
13
|
+
* @class
|
|
14
|
+
* @extends GenericPushNotification
|
|
15
|
+
* @example
|
|
16
|
+
* device.on('pushNotification', (notification) => {
|
|
17
|
+
* if (notification instanceof BindPushNotification) {
|
|
18
|
+
* const timeInfo = notification.time;
|
|
19
|
+
* if (timeInfo) {
|
|
20
|
+
* console.log('Device bound at:', timeInfo.timestamp, 'timezone:', timeInfo.timezone);
|
|
21
|
+
* }
|
|
22
|
+
* const hwInfo = notification.hwinfo;
|
|
23
|
+
* if (hwInfo) {
|
|
24
|
+
* console.log('Hardware version:', hwInfo.version, 'MAC:', hwInfo.macAddress);
|
|
25
|
+
* }
|
|
26
|
+
* const fwInfo = notification.fwinfo;
|
|
27
|
+
* if (fwInfo) {
|
|
28
|
+
* console.log('Firmware version:', fwInfo.version, 'WiFi MAC:', fwInfo.wifiMac);
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
class BindPushNotification extends GenericPushNotification {
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new BindPushNotification instance.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} originatingDeviceUuid - UUID of the device that sent the notification
|
|
38
|
+
* @param {Object} rawData - Raw notification data from the device
|
|
39
|
+
* @param {Object} [rawData.bind] - Binding data
|
|
40
|
+
* @param {Object} [rawData.bind.time] - Time information object (will be converted to TimeInfo instance)
|
|
41
|
+
* @param {Object} [rawData.bind.hardware] - Hardware information object (will be converted to HardwareInfo instance)
|
|
42
|
+
* @param {Object} [rawData.bind.firmware] - Firmware information object (will be converted to FirmwareInfo instance)
|
|
43
|
+
*/
|
|
44
|
+
constructor(originatingDeviceUuid, rawData) {
|
|
45
|
+
super('Appliance.Control.Bind', originatingDeviceUuid, rawData);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Gets the binding timestamp information.
|
|
50
|
+
*
|
|
51
|
+
* Returns a TimeInfo instance that normalizes timezone and timestamp data from the raw payload.
|
|
52
|
+
*
|
|
53
|
+
* @returns {TimeInfo|null} TimeInfo instance or null if not available
|
|
54
|
+
*/
|
|
55
|
+
get time() {
|
|
56
|
+
const timeData = this._rawData?.bind?.time;
|
|
57
|
+
if (!timeData) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return TimeInfo.fromDict(timeData);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets the hardware information.
|
|
65
|
+
*
|
|
66
|
+
* Returns a HardwareInfo instance that normalizes hardware metadata (version, UUID, MAC, chip type)
|
|
67
|
+
* from the raw payload, handling both camelCase and snake_case property names.
|
|
68
|
+
*
|
|
69
|
+
* @returns {HardwareInfo|null} HardwareInfo instance or null if not available
|
|
70
|
+
*/
|
|
71
|
+
get hwinfo() {
|
|
72
|
+
const hardwareData = this._rawData?.bind?.hardware;
|
|
73
|
+
if (!hardwareData) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return HardwareInfo.fromDict(hardwareData);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets the firmware information.
|
|
81
|
+
*
|
|
82
|
+
* Returns a FirmwareInfo instance that normalizes firmware metadata (version, WiFi MAC, server info)
|
|
83
|
+
* from the raw payload, handling both camelCase and snake_case property names.
|
|
84
|
+
*
|
|
85
|
+
* @returns {FirmwareInfo|null} FirmwareInfo instance or null if not available
|
|
86
|
+
*/
|
|
87
|
+
get fwinfo() {
|
|
88
|
+
const firmwareData = this._rawData?.bind?.firmware;
|
|
89
|
+
if (!firmwareData) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return FirmwareInfo.fromDict(firmwareData);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = BindPushNotification;
|
|
97
|
+
|