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,282 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents hardware information from device binding data.
|
|
5
|
+
*
|
|
6
|
+
* Encapsulates hardware metadata from device binding notifications. The API may return
|
|
7
|
+
* properties in either camelCase or snake_case format, so this class normalizes them
|
|
8
|
+
* to camelCase for consistent access.
|
|
9
|
+
*
|
|
10
|
+
* @class
|
|
11
|
+
* @example
|
|
12
|
+
* // Use fromDict() to create instances
|
|
13
|
+
* const hardwareInfo = HardwareInfo.fromDict({
|
|
14
|
+
* version: '1.0.0',
|
|
15
|
+
* uuid: '12345',
|
|
16
|
+
* type: 'mss310',
|
|
17
|
+
* sub_type: 'v1',
|
|
18
|
+
* mac_address: 'AA:BB:CC:DD:EE:FF',
|
|
19
|
+
* chip_type: 'ESP32'
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
class HardwareInfo {
|
|
23
|
+
/**
|
|
24
|
+
* Creates a HardwareInfo instance from a dictionary object.
|
|
25
|
+
*
|
|
26
|
+
* Normalizes property names from both camelCase and snake_case formats to ensure
|
|
27
|
+
* consistent access regardless of API response format. Uses Object.create() instead
|
|
28
|
+
* of a constructor to allow static factory pattern.
|
|
29
|
+
*
|
|
30
|
+
* @static
|
|
31
|
+
* @param {Object} jsonDict - Raw API response object with any key format
|
|
32
|
+
* @param {string} [jsonDict.version] - Hardware version
|
|
33
|
+
* @param {string} [jsonDict.uuid] - Device UUID
|
|
34
|
+
* @param {string} [jsonDict.type] - Hardware type
|
|
35
|
+
* @param {string} [jsonDict.subType] - Hardware sub-type (camelCase)
|
|
36
|
+
* @param {string} [jsonDict.sub_type] - Hardware sub-type (snake_case)
|
|
37
|
+
* @param {string} [jsonDict.macAddress] - MAC address (camelCase)
|
|
38
|
+
* @param {string} [jsonDict.mac_address] - MAC address (snake_case)
|
|
39
|
+
* @param {string} [jsonDict.chipType] - Chip type (camelCase)
|
|
40
|
+
* @param {string} [jsonDict.chip_type] - Chip type (snake_case)
|
|
41
|
+
* @param {string} [jsonDict.chip_time] - Chip time (snake_case, mapped to chipType)
|
|
42
|
+
* @returns {HardwareInfo} New HardwareInfo instance
|
|
43
|
+
*/
|
|
44
|
+
static fromDict(jsonDict) {
|
|
45
|
+
if (!jsonDict || typeof jsonDict !== 'object') {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Map camelCase keys to their possible API variants (camelCase, snake_case)
|
|
50
|
+
const propertyMappings = {
|
|
51
|
+
version: ['version'],
|
|
52
|
+
uuid: ['uuid'],
|
|
53
|
+
type: ['type'],
|
|
54
|
+
subType: ['subType', 'sub_type'],
|
|
55
|
+
macAddress: ['macAddress', 'mac_address'],
|
|
56
|
+
chipType: ['chipType', 'chip_type', 'chip_time']
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Check each mapping in priority order to find the first non-null value
|
|
60
|
+
const normalized = {};
|
|
61
|
+
for (const [camelKey, alternatives] of Object.entries(propertyMappings)) {
|
|
62
|
+
for (const key of alternatives) {
|
|
63
|
+
if (jsonDict[key] !== null && jsonDict[key] !== undefined) {
|
|
64
|
+
normalized[camelKey] = jsonDict[key];
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Use Object.create() to avoid constructor, allowing static factory pattern
|
|
71
|
+
const instance = Object.create(HardwareInfo.prototype);
|
|
72
|
+
instance.version = normalized.version || null;
|
|
73
|
+
instance.uuid = normalized.uuid || null;
|
|
74
|
+
instance.type = normalized.type || null;
|
|
75
|
+
instance.subType = normalized.subType || null;
|
|
76
|
+
instance.macAddress = normalized.macAddress || null;
|
|
77
|
+
instance.chipType = normalized.chipType || null;
|
|
78
|
+
|
|
79
|
+
return instance;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Converts the instance to a plain object dictionary with camelCase keys.
|
|
84
|
+
*
|
|
85
|
+
* @returns {Object} Plain object with camelCase property keys
|
|
86
|
+
*/
|
|
87
|
+
toDict() {
|
|
88
|
+
return {
|
|
89
|
+
version: this.version,
|
|
90
|
+
uuid: this.uuid,
|
|
91
|
+
type: this.type,
|
|
92
|
+
subType: this.subType,
|
|
93
|
+
macAddress: this.macAddress,
|
|
94
|
+
chipType: this.chipType
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Represents firmware information from device binding data.
|
|
101
|
+
*
|
|
102
|
+
* Encapsulates firmware metadata from device binding notifications. The API may return
|
|
103
|
+
* properties in either camelCase or snake_case format, so this class normalizes them
|
|
104
|
+
* to camelCase for consistent access.
|
|
105
|
+
*
|
|
106
|
+
* @class
|
|
107
|
+
* @example
|
|
108
|
+
* // Use fromDict() to create instances
|
|
109
|
+
* const firmwareInfo = FirmwareInfo.fromDict({
|
|
110
|
+
* wifi_mac: 'AA:BB:CC:DD:EE:FF',
|
|
111
|
+
* version: '2.0.0',
|
|
112
|
+
* user_id: 'user123',
|
|
113
|
+
* server: 'mqtt.meross.com',
|
|
114
|
+
* port: 443,
|
|
115
|
+
* inner_ip: '192.168.1.100',
|
|
116
|
+
* compile_time: '2024-01-01'
|
|
117
|
+
* });
|
|
118
|
+
*/
|
|
119
|
+
class FirmwareInfo {
|
|
120
|
+
/**
|
|
121
|
+
* Creates a FirmwareInfo instance from a dictionary object.
|
|
122
|
+
*
|
|
123
|
+
* Normalizes property names from both camelCase and snake_case formats to ensure
|
|
124
|
+
* consistent access regardless of API response format. Uses Object.create() instead
|
|
125
|
+
* of a constructor to allow static factory pattern.
|
|
126
|
+
*
|
|
127
|
+
* @static
|
|
128
|
+
* @param {Object} jsonDict - Raw API response object with any key format
|
|
129
|
+
* @param {string} [jsonDict.wifiMac] - WiFi MAC address (camelCase)
|
|
130
|
+
* @param {string} [jsonDict.wifi_mac] - WiFi MAC address (snake_case)
|
|
131
|
+
* @param {string} [jsonDict.version] - Firmware version
|
|
132
|
+
* @param {string} [jsonDict.userId] - User ID (camelCase)
|
|
133
|
+
* @param {string} [jsonDict.user_id] - User ID (snake_case)
|
|
134
|
+
* @param {string} [jsonDict.server] - Server address
|
|
135
|
+
* @param {number} [jsonDict.port] - Port number
|
|
136
|
+
* @param {string} [jsonDict.innerIp] - Inner IP address (camelCase)
|
|
137
|
+
* @param {string} [jsonDict.inner_ip] - Inner IP address (snake_case)
|
|
138
|
+
* @param {string} [jsonDict.compileTime] - Compile time (camelCase)
|
|
139
|
+
* @param {string} [jsonDict.compile_time] - Compile time (snake_case)
|
|
140
|
+
* @returns {FirmwareInfo} New FirmwareInfo instance
|
|
141
|
+
*/
|
|
142
|
+
static fromDict(jsonDict) {
|
|
143
|
+
if (!jsonDict || typeof jsonDict !== 'object') {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Map camelCase keys to their possible API variants (camelCase, snake_case)
|
|
148
|
+
const propertyMappings = {
|
|
149
|
+
wifiMac: ['wifiMac', 'wifi_mac'],
|
|
150
|
+
version: ['version'],
|
|
151
|
+
userId: ['userId', 'user_id'],
|
|
152
|
+
server: ['server'],
|
|
153
|
+
port: ['port'],
|
|
154
|
+
innerIp: ['innerIp', 'inner_ip'],
|
|
155
|
+
compileTime: ['compileTime', 'compile_time']
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Check each mapping in priority order to find the first non-null value
|
|
159
|
+
const normalized = {};
|
|
160
|
+
for (const [camelKey, alternatives] of Object.entries(propertyMappings)) {
|
|
161
|
+
for (const key of alternatives) {
|
|
162
|
+
if (jsonDict[key] !== null && jsonDict[key] !== undefined) {
|
|
163
|
+
normalized[camelKey] = jsonDict[key];
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Use Object.create() to avoid constructor, allowing static factory pattern
|
|
170
|
+
const instance = Object.create(FirmwareInfo.prototype);
|
|
171
|
+
instance.wifiMac = normalized.wifiMac || null;
|
|
172
|
+
instance.version = normalized.version || null;
|
|
173
|
+
instance.userId = normalized.userId || null;
|
|
174
|
+
instance.server = normalized.server || null;
|
|
175
|
+
instance.port = normalized.port || null;
|
|
176
|
+
instance.innerIp = normalized.innerIp || null;
|
|
177
|
+
instance.compileTime = normalized.compileTime || null;
|
|
178
|
+
|
|
179
|
+
return instance;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Converts the instance to a plain object dictionary with camelCase keys.
|
|
184
|
+
*
|
|
185
|
+
* @returns {Object} Plain object with camelCase property keys
|
|
186
|
+
*/
|
|
187
|
+
toDict() {
|
|
188
|
+
return {
|
|
189
|
+
wifiMac: this.wifiMac,
|
|
190
|
+
version: this.version,
|
|
191
|
+
userId: this.userId,
|
|
192
|
+
server: this.server,
|
|
193
|
+
port: this.port,
|
|
194
|
+
innerIp: this.innerIp,
|
|
195
|
+
compileTime: this.compileTime
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Represents time information from device binding data.
|
|
202
|
+
*
|
|
203
|
+
* Encapsulates time metadata from device binding notifications. The API may return
|
|
204
|
+
* properties in either camelCase or snake_case format, so this class normalizes them
|
|
205
|
+
* to camelCase for consistent access.
|
|
206
|
+
*
|
|
207
|
+
* @class
|
|
208
|
+
* @example
|
|
209
|
+
* // Use fromDict() to create instances
|
|
210
|
+
* const timeInfo = TimeInfo.fromDict({
|
|
211
|
+
* timezone: 'America/New_York',
|
|
212
|
+
* timestamp: 1234567890,
|
|
213
|
+
* time_rule: 'DST'
|
|
214
|
+
* });
|
|
215
|
+
*/
|
|
216
|
+
class TimeInfo {
|
|
217
|
+
/**
|
|
218
|
+
* Creates a TimeInfo instance from a dictionary object.
|
|
219
|
+
*
|
|
220
|
+
* Normalizes property names from both camelCase and snake_case formats to ensure
|
|
221
|
+
* consistent access regardless of API response format. Uses Object.create() instead
|
|
222
|
+
* of a constructor to allow static factory pattern.
|
|
223
|
+
*
|
|
224
|
+
* @static
|
|
225
|
+
* @param {Object} jsonDict - Raw API response object with any key format
|
|
226
|
+
* @param {string} [jsonDict.timezone] - Timezone string
|
|
227
|
+
* @param {number} [jsonDict.timestamp] - Unix timestamp
|
|
228
|
+
* @param {string} [jsonDict.timeRule] - Time rule (camelCase)
|
|
229
|
+
* @param {string} [jsonDict.time_rule] - Time rule (snake_case)
|
|
230
|
+
* @returns {TimeInfo} New TimeInfo instance
|
|
231
|
+
*/
|
|
232
|
+
static fromDict(jsonDict) {
|
|
233
|
+
if (!jsonDict || typeof jsonDict !== 'object') {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Map camelCase keys to their possible API variants (camelCase, snake_case)
|
|
238
|
+
const propertyMappings = {
|
|
239
|
+
timezone: ['timezone'],
|
|
240
|
+
timestamp: ['timestamp'],
|
|
241
|
+
timeRule: ['timeRule', 'time_rule']
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// Check each mapping in priority order to find the first non-null value
|
|
245
|
+
const normalized = {};
|
|
246
|
+
for (const [camelKey, alternatives] of Object.entries(propertyMappings)) {
|
|
247
|
+
for (const key of alternatives) {
|
|
248
|
+
if (jsonDict[key] !== null && jsonDict[key] !== undefined) {
|
|
249
|
+
normalized[camelKey] = jsonDict[key];
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Use Object.create() to avoid constructor, allowing static factory pattern
|
|
256
|
+
const instance = Object.create(TimeInfo.prototype);
|
|
257
|
+
instance.timezone = normalized.timezone || null;
|
|
258
|
+
instance.timestamp = normalized.timestamp || null;
|
|
259
|
+
instance.timeRule = normalized.timeRule || null;
|
|
260
|
+
|
|
261
|
+
return instance;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Converts the instance to a plain object dictionary with camelCase keys.
|
|
266
|
+
*
|
|
267
|
+
* @returns {Object} Plain object with camelCase property keys
|
|
268
|
+
*/
|
|
269
|
+
toDict() {
|
|
270
|
+
return {
|
|
271
|
+
timezone: this.timezone,
|
|
272
|
+
timestamp: this.timestamp,
|
|
273
|
+
timeRule: this.timeRule
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = {
|
|
279
|
+
HardwareInfo,
|
|
280
|
+
FirmwareInfo,
|
|
281
|
+
TimeInfo
|
|
282
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const GenericPushNotification = require('./generic');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Push notification for diffuser light state changes.
|
|
7
|
+
*
|
|
8
|
+
* Emitted when a diffuser device's light settings change (on/off, mode, color, brightness).
|
|
9
|
+
* Contains the updated light state for one or more channels.
|
|
10
|
+
*
|
|
11
|
+
* @class
|
|
12
|
+
* @extends GenericPushNotification
|
|
13
|
+
* @example
|
|
14
|
+
* device.on('pushNotification', (notification) => {
|
|
15
|
+
* if (notification instanceof DiffuserLightPushNotification) {
|
|
16
|
+
* const lightData = notification.lightData;
|
|
17
|
+
* lightData.forEach(light => {
|
|
18
|
+
* console.log(`Channel ${light.channel} light is ${light.onoff === 1 ? 'on' : 'off'}`);
|
|
19
|
+
* });
|
|
20
|
+
* }
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
class DiffuserLightPushNotification extends GenericPushNotification {
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new DiffuserLightPushNotification instance.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} originatingDeviceUuid - UUID of the device that sent the notification
|
|
28
|
+
* @param {Object} rawData - Raw notification data from the device
|
|
29
|
+
* @param {Object|Array} [rawData.light] - Light state data (single object or array)
|
|
30
|
+
* @param {number} [rawData.light.channel] - Channel number
|
|
31
|
+
* @param {number} [rawData.light.onoff] - On/off state (0=off, 1=on)
|
|
32
|
+
* @param {number} [rawData.light.mode] - Light mode
|
|
33
|
+
* @param {number} [rawData.light.rgb] - RGB color value
|
|
34
|
+
* @param {number} [rawData.light.luminance] - Brightness value
|
|
35
|
+
*/
|
|
36
|
+
constructor(originatingDeviceUuid, rawData) {
|
|
37
|
+
super('Appliance.Control.Diffuser.Light', originatingDeviceUuid, rawData);
|
|
38
|
+
|
|
39
|
+
// Devices may send single objects or arrays; normalize to array for consistent processing
|
|
40
|
+
const lightRaw = rawData?.light;
|
|
41
|
+
const light = GenericPushNotification.normalizeToArray(lightRaw);
|
|
42
|
+
|
|
43
|
+
// Update rawData so routing logic receives normalized structure
|
|
44
|
+
if (rawData && lightRaw !== light) {
|
|
45
|
+
rawData.light = light;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this._lightData = light;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets the light state data array.
|
|
53
|
+
*
|
|
54
|
+
* @returns {Array} Array of light state objects (empty array if no data)
|
|
55
|
+
*/
|
|
56
|
+
get lightData() {
|
|
57
|
+
return this._lightData;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extracts diffuser light changes from this notification.
|
|
62
|
+
*
|
|
63
|
+
* Converts raw device data format (onoff, mode, rgb, luminance) to normalized change
|
|
64
|
+
* format used by subscription managers. Maps onoff (0/1) to boolean isOn for consistency.
|
|
65
|
+
*
|
|
66
|
+
* @returns {Object} Changes object with light state, e.g., { diffuserLight: { 0: {...} } }
|
|
67
|
+
*/
|
|
68
|
+
extractChanges() {
|
|
69
|
+
const changes = {};
|
|
70
|
+
if (!this._lightData || this._lightData.length === 0) {
|
|
71
|
+
return changes;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
changes.diffuserLight = {};
|
|
75
|
+
this._lightData.forEach(item => {
|
|
76
|
+
const channel = item.channel !== undefined ? item.channel : 0;
|
|
77
|
+
const lightChange = {};
|
|
78
|
+
if (item.onoff !== undefined) {
|
|
79
|
+
lightChange.isOn = item.onoff === 1;
|
|
80
|
+
}
|
|
81
|
+
if (item.mode !== undefined) {
|
|
82
|
+
lightChange.mode = item.mode;
|
|
83
|
+
}
|
|
84
|
+
if (item.rgb !== undefined) {
|
|
85
|
+
lightChange.rgb = item.rgb;
|
|
86
|
+
}
|
|
87
|
+
if (item.luminance !== undefined) {
|
|
88
|
+
lightChange.luminance = item.luminance;
|
|
89
|
+
}
|
|
90
|
+
if (Object.keys(lightChange).length > 0) {
|
|
91
|
+
changes.diffuserLight[channel] = lightChange;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return changes;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = DiffuserLightPushNotification;
|
|
100
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const GenericPushNotification = require('./generic');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Push notification for diffuser spray state changes.
|
|
7
|
+
*
|
|
8
|
+
* Emitted when a diffuser device's spray mode changes.
|
|
9
|
+
* Contains the updated spray state for one or more channels.
|
|
10
|
+
*
|
|
11
|
+
* @class
|
|
12
|
+
* @extends GenericPushNotification
|
|
13
|
+
* @example
|
|
14
|
+
* device.on('pushNotification', (notification) => {
|
|
15
|
+
* if (notification instanceof DiffuserSprayPushNotification) {
|
|
16
|
+
* const sprayData = notification.sprayData;
|
|
17
|
+
* sprayData.forEach(spray => {
|
|
18
|
+
* console.log(`Channel ${spray.channel} spray mode: ${spray.mode}`);
|
|
19
|
+
* });
|
|
20
|
+
* }
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
class DiffuserSprayPushNotification extends GenericPushNotification {
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new DiffuserSprayPushNotification instance.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} originatingDeviceUuid - UUID of the device that sent the notification
|
|
28
|
+
* @param {Object} rawData - Raw notification data from the device
|
|
29
|
+
* @param {Object|Array} [rawData.spray] - Spray state data (single object or array)
|
|
30
|
+
* @param {number} [rawData.spray.channel] - Channel number
|
|
31
|
+
* @param {number} [rawData.spray.mode] - Spray mode value
|
|
32
|
+
*/
|
|
33
|
+
constructor(originatingDeviceUuid, rawData) {
|
|
34
|
+
super('Appliance.Control.Diffuser.Spray', originatingDeviceUuid, rawData);
|
|
35
|
+
|
|
36
|
+
// Devices may send single objects or arrays; normalize to array for consistent processing
|
|
37
|
+
const sprayRaw = rawData?.spray;
|
|
38
|
+
const spray = GenericPushNotification.normalizeToArray(sprayRaw);
|
|
39
|
+
|
|
40
|
+
// Update rawData so routing logic receives normalized structure
|
|
41
|
+
if (rawData && sprayRaw !== spray) {
|
|
42
|
+
rawData.spray = spray;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this._sprayData = spray;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Gets the spray state data array.
|
|
50
|
+
*
|
|
51
|
+
* @returns {Array} Array of spray state objects (empty array if no data)
|
|
52
|
+
*/
|
|
53
|
+
get sprayData() {
|
|
54
|
+
return this._sprayData;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extracts diffuser spray changes from this notification.
|
|
59
|
+
*
|
|
60
|
+
* Converts raw device data format to normalized change format used by subscription managers.
|
|
61
|
+
*
|
|
62
|
+
* @returns {Object} Changes object with spray state, e.g., { diffuserSpray: { 0: { mode: 1 } } }
|
|
63
|
+
*/
|
|
64
|
+
extractChanges() {
|
|
65
|
+
const changes = {};
|
|
66
|
+
if (!this._sprayData || this._sprayData.length === 0) {
|
|
67
|
+
return changes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
changes.diffuserSpray = {};
|
|
71
|
+
this._sprayData.forEach(item => {
|
|
72
|
+
const channel = item.channel !== undefined ? item.channel : 0;
|
|
73
|
+
if (item.mode !== undefined) {
|
|
74
|
+
changes.diffuserSpray[channel] = { mode: item.mode };
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return changes;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = DiffuserSprayPushNotification;
|
|
83
|
+
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const GenericPushNotification = require('./generic');
|
|
4
|
+
const OnlinePushNotification = require('./online');
|
|
5
|
+
const AlarmPushNotification = require('./alarm');
|
|
6
|
+
const BindPushNotification = require('./bind');
|
|
7
|
+
const UnbindPushNotification = require('./unbind');
|
|
8
|
+
const WaterLeakPushNotification = require('./water-leak');
|
|
9
|
+
const HubOnlinePushNotification = require('./hub-online');
|
|
10
|
+
const HubToggleXPushNotification = require('./hub-togglex');
|
|
11
|
+
const HubBatteryPushNotification = require('./hub-battery');
|
|
12
|
+
const HubSensorAllPushNotification = require('./hub-sensor-all');
|
|
13
|
+
const HubSensorTempHumPushNotification = require('./hub-sensor-temphum');
|
|
14
|
+
const HubSensorAlertPushNotification = require('./hub-sensor-alert');
|
|
15
|
+
const HubSensorSmokePushNotification = require('./hub-sensor-smoke');
|
|
16
|
+
const HubMts100AllPushNotification = require('./hub-mts100-all');
|
|
17
|
+
const HubMts100ModePushNotification = require('./hub-mts100-mode');
|
|
18
|
+
const HubMts100TemperaturePushNotification = require('./hub-mts100-temperature');
|
|
19
|
+
const HubSubdeviceListPushNotification = require('./hub-subdevicelist');
|
|
20
|
+
const SensorLatestXPushNotification = require('./sensor-latestx');
|
|
21
|
+
const TimerXPushNotification = require('./timerx');
|
|
22
|
+
const TriggerXPushNotification = require('./triggerx');
|
|
23
|
+
const ToggleXPushNotification = require('./togglex');
|
|
24
|
+
const PresenceStudyPushNotification = require('./presence-study');
|
|
25
|
+
const DiffuserLightPushNotification = require('./diffuser-light');
|
|
26
|
+
const DiffuserSprayPushNotification = require('./diffuser-spray');
|
|
27
|
+
|
|
28
|
+
const PUSH_NOTIFICATION_BINDING = {
|
|
29
|
+
'Appliance.System.Online': OnlinePushNotification,
|
|
30
|
+
'Appliance.Control.Alarm': AlarmPushNotification,
|
|
31
|
+
'Appliance.Control.Bind': BindPushNotification,
|
|
32
|
+
'Appliance.Control.Unbind': UnbindPushNotification,
|
|
33
|
+
'Appliance.Control.ToggleX': ToggleXPushNotification,
|
|
34
|
+
'Appliance.Control.TimerX': TimerXPushNotification,
|
|
35
|
+
'Appliance.Control.TriggerX': TriggerXPushNotification,
|
|
36
|
+
'Appliance.Hub.Sensor.WaterLeak': WaterLeakPushNotification,
|
|
37
|
+
'Appliance.Hub.Online': HubOnlinePushNotification,
|
|
38
|
+
'Appliance.Hub.ToggleX': HubToggleXPushNotification,
|
|
39
|
+
'Appliance.Hub.Battery': HubBatteryPushNotification,
|
|
40
|
+
'Appliance.Hub.Sensor.All': HubSensorAllPushNotification,
|
|
41
|
+
'Appliance.Hub.Sensor.TempHum': HubSensorTempHumPushNotification,
|
|
42
|
+
'Appliance.Hub.Sensor.Alert': HubSensorAlertPushNotification,
|
|
43
|
+
'Appliance.Hub.Sensor.Smoke': HubSensorSmokePushNotification,
|
|
44
|
+
'Appliance.Hub.Mts100.All': HubMts100AllPushNotification,
|
|
45
|
+
'Appliance.Hub.Mts100.Mode': HubMts100ModePushNotification,
|
|
46
|
+
'Appliance.Hub.Mts100.Temperature': HubMts100TemperaturePushNotification,
|
|
47
|
+
'Appliance.Hub.SubdeviceList': HubSubdeviceListPushNotification,
|
|
48
|
+
'Appliance.Control.Sensor.LatestX': SensorLatestXPushNotification,
|
|
49
|
+
'Appliance.Control.Presence.Study': PresenceStudyPushNotification,
|
|
50
|
+
'Appliance.Control.Diffuser.Light': DiffuserLightPushNotification,
|
|
51
|
+
'Appliance.Control.Diffuser.Spray': DiffuserSprayPushNotification
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Maps hub notification namespaces to their corresponding data keys in raw payloads.
|
|
56
|
+
*
|
|
57
|
+
* Hub notifications use different top-level keys in their payloads (e.g., 'online', 'togglex', 'battery').
|
|
58
|
+
* This mapping allows extraction of the correct data array for routing to subdevices.
|
|
59
|
+
*/
|
|
60
|
+
const HUB_NAMESPACE_DATA_KEY_MAP = {
|
|
61
|
+
'Appliance.Hub.Online': 'online',
|
|
62
|
+
'Appliance.Hub.ToggleX': 'togglex',
|
|
63
|
+
'Appliance.Hub.Battery': 'battery',
|
|
64
|
+
'Appliance.Hub.Sensor.All': 'all',
|
|
65
|
+
'Appliance.Hub.Sensor.TempHum': 'tempHum',
|
|
66
|
+
'Appliance.Hub.Sensor.Alert': 'alert',
|
|
67
|
+
'Appliance.Hub.Sensor.Smoke': 'smokeAlarm',
|
|
68
|
+
'Appliance.Hub.Sensor.WaterLeak': 'waterLeak',
|
|
69
|
+
'Appliance.Hub.Mts100.All': 'all',
|
|
70
|
+
'Appliance.Hub.Mts100.Mode': 'mode',
|
|
71
|
+
'Appliance.Hub.Mts100.Temperature': 'temperature',
|
|
72
|
+
'Appliance.Hub.SubdeviceList': 'subdeviceList',
|
|
73
|
+
'Appliance.Control.Sensor.LatestX': 'latest'
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Strategy functions for extracting data arrays from raw payloads.
|
|
78
|
+
*
|
|
79
|
+
* Some hub notifications have non-standard data structures that don't follow the standard
|
|
80
|
+
* namespace-to-key mapping. These strategies handle special cases like nested structures
|
|
81
|
+
* or inconsistent property names. Data normalization (single object to array) happens
|
|
82
|
+
* at parse time in constructors.
|
|
83
|
+
*/
|
|
84
|
+
const DATA_EXTRACTION_STRATEGIES = {
|
|
85
|
+
/**
|
|
86
|
+
* Extracts subdevice list data.
|
|
87
|
+
*
|
|
88
|
+
* Handles the inconsistent structure where subdevices may be nested in a 'subdevice'
|
|
89
|
+
* property or directly in 'subdeviceList'.
|
|
90
|
+
*/
|
|
91
|
+
'Appliance.Hub.SubdeviceList': (rawData) => {
|
|
92
|
+
if (rawData.subdeviceList) {
|
|
93
|
+
return rawData.subdeviceList.subdevice || rawData.subdeviceList;
|
|
94
|
+
}
|
|
95
|
+
return rawData.subdeviceList;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Parses a push notification from raw MQTT message data.
|
|
101
|
+
*
|
|
102
|
+
* Factory function that instantiates the appropriate notification class based on namespace.
|
|
103
|
+
* Falls back to GenericPushNotification if no specific class exists or if instantiation fails,
|
|
104
|
+
* ensuring all notifications can be handled even for unknown or malformed payloads.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} namespace - The namespace of the push notification (e.g., 'Appliance.Control.ToggleX')
|
|
107
|
+
* @param {Object} messagePayload - The raw message payload from MQTT
|
|
108
|
+
* @param {string} deviceUuid - The UUID of the device that originated the notification
|
|
109
|
+
* @returns {GenericPushNotification|null} The parsed notification object, or null if invalid
|
|
110
|
+
* @example
|
|
111
|
+
* const notification = parsePushNotification(
|
|
112
|
+
* 'Appliance.Control.ToggleX',
|
|
113
|
+
* { togglex: [{ channel: 0, onoff: 1 }] },
|
|
114
|
+
* 'device-uuid-123'
|
|
115
|
+
* );
|
|
116
|
+
* if (notification instanceof ToggleXPushNotification) {
|
|
117
|
+
* console.log('Toggle state changed');
|
|
118
|
+
* }
|
|
119
|
+
*/
|
|
120
|
+
function parsePushNotification(namespace, messagePayload, deviceUuid) {
|
|
121
|
+
if (!namespace || typeof namespace !== 'string') {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!deviceUuid || typeof deviceUuid !== 'string') {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const NotificationClass = PUSH_NOTIFICATION_BINDING[namespace];
|
|
130
|
+
|
|
131
|
+
if (NotificationClass) {
|
|
132
|
+
try {
|
|
133
|
+
return new NotificationClass(deviceUuid, messagePayload);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Fall back to generic notification if specific class fails to parse
|
|
136
|
+
return new GenericPushNotification(namespace, deviceUuid, messagePayload);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Return generic notification for unmapped namespaces (unknown notification types)
|
|
141
|
+
return new GenericPushNotification(namespace, deviceUuid, messagePayload);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Extracts the data array from a raw payload based on namespace.
|
|
146
|
+
*
|
|
147
|
+
* Uses namespace-specific extraction strategies for non-standard structures, otherwise
|
|
148
|
+
* falls back to the standard data key mapping. Returns null if extraction fails.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} namespace - The namespace of the push notification
|
|
151
|
+
* @param {Object} rawData - The raw data payload
|
|
152
|
+
* @returns {Array|null} Data array or null if extraction fails
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
function extractDataArray(namespace, rawData) {
|
|
156
|
+
// Check for custom extraction strategy first (handles non-standard structures)
|
|
157
|
+
const extractionStrategy = DATA_EXTRACTION_STRATEGIES[namespace];
|
|
158
|
+
if (extractionStrategy) {
|
|
159
|
+
return extractionStrategy(rawData);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Fall back to standard namespace-to-key mapping
|
|
163
|
+
const dataKey = HUB_NAMESPACE_DATA_KEY_MAP[namespace];
|
|
164
|
+
if (!dataKey) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return rawData[dataKey];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Routes a hub push notification to the appropriate subdevices.
|
|
173
|
+
*
|
|
174
|
+
* Extracts subdevice data from a hub notification and forwards it to the corresponding
|
|
175
|
+
* subdevice instances. Each subdevice processes the notification asynchronously via
|
|
176
|
+
* handleSubdeviceNotification. Skips unregistered subdevices and logs warnings.
|
|
177
|
+
*
|
|
178
|
+
* @param {GenericPushNotification} notification - The push notification instance
|
|
179
|
+
* @param {MerossHubDevice} hubDevice - The hub device instance
|
|
180
|
+
* @example
|
|
181
|
+
* const notification = parsePushNotification(namespace, payload, hubUuid);
|
|
182
|
+
* if (notification.namespace.startsWith('Appliance.Hub.')) {
|
|
183
|
+
* routeToSubdevices(notification, hubDevice);
|
|
184
|
+
* }
|
|
185
|
+
*/
|
|
186
|
+
function routeToSubdevices(notification, hubDevice) {
|
|
187
|
+
if (!notification || !hubDevice || typeof hubDevice.getSubdevice !== 'function') {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { namespace } = notification;
|
|
192
|
+
const rawData = notification.rawData || {};
|
|
193
|
+
|
|
194
|
+
// Extract data array using namespace-specific extraction logic
|
|
195
|
+
const dataArray = extractDataArray(namespace, rawData);
|
|
196
|
+
|
|
197
|
+
if (!Array.isArray(dataArray)) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const item of dataArray) {
|
|
202
|
+
// Handle inconsistent subdevice ID field names across notification types
|
|
203
|
+
const subdeviceId = item.subId || item.id;
|
|
204
|
+
if (!subdeviceId) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const subdevice = hubDevice.getSubdevice(subdeviceId);
|
|
209
|
+
if (!subdevice) {
|
|
210
|
+
const logger = hubDevice.cloudInst?.options?.logger || console.warn;
|
|
211
|
+
logger(`Received update for subdevice (id ${subdeviceId}) that has not been registered with hub ${hubDevice.uuid}. Update will be skipped.`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Process asynchronously and catch errors to prevent one failure from blocking others
|
|
216
|
+
if (typeof subdevice.handleSubdeviceNotification === 'function') {
|
|
217
|
+
subdevice.handleSubdeviceNotification(namespace, item).catch(err => {
|
|
218
|
+
const logger = hubDevice.cloudInst?.options?.logger || console.error;
|
|
219
|
+
logger(`Error routing hub ${namespace} notification to subdevice ${subdeviceId}: ${err.message}`);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
parsePushNotification,
|
|
227
|
+
routeToSubdevices
|
|
228
|
+
};
|
|
229
|
+
|