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.
Files changed (99) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +153 -0
  4. package/index.d.ts +2344 -0
  5. package/index.js +131 -0
  6. package/lib/controller/device.js +1317 -0
  7. package/lib/controller/features/alarm-feature.js +89 -0
  8. package/lib/controller/features/child-lock-feature.js +61 -0
  9. package/lib/controller/features/config-feature.js +54 -0
  10. package/lib/controller/features/consumption-feature.js +210 -0
  11. package/lib/controller/features/control-feature.js +62 -0
  12. package/lib/controller/features/diffuser-feature.js +411 -0
  13. package/lib/controller/features/digest-timer-feature.js +22 -0
  14. package/lib/controller/features/digest-trigger-feature.js +22 -0
  15. package/lib/controller/features/dnd-feature.js +79 -0
  16. package/lib/controller/features/electricity-feature.js +144 -0
  17. package/lib/controller/features/encryption-feature.js +259 -0
  18. package/lib/controller/features/garage-feature.js +337 -0
  19. package/lib/controller/features/hub-feature.js +687 -0
  20. package/lib/controller/features/light-feature.js +408 -0
  21. package/lib/controller/features/presence-sensor-feature.js +297 -0
  22. package/lib/controller/features/roller-shutter-feature.js +456 -0
  23. package/lib/controller/features/runtime-feature.js +74 -0
  24. package/lib/controller/features/screen-feature.js +67 -0
  25. package/lib/controller/features/sensor-history-feature.js +47 -0
  26. package/lib/controller/features/smoke-config-feature.js +50 -0
  27. package/lib/controller/features/spray-feature.js +166 -0
  28. package/lib/controller/features/system-feature.js +269 -0
  29. package/lib/controller/features/temp-unit-feature.js +55 -0
  30. package/lib/controller/features/thermostat-feature.js +804 -0
  31. package/lib/controller/features/timer-feature.js +507 -0
  32. package/lib/controller/features/toggle-feature.js +223 -0
  33. package/lib/controller/features/trigger-feature.js +333 -0
  34. package/lib/controller/hub-device.js +185 -0
  35. package/lib/controller/subdevice.js +1537 -0
  36. package/lib/device-factory.js +463 -0
  37. package/lib/error-budget.js +138 -0
  38. package/lib/http-api.js +766 -0
  39. package/lib/manager.js +1609 -0
  40. package/lib/model/channel-info.js +79 -0
  41. package/lib/model/constants.js +119 -0
  42. package/lib/model/enums.js +819 -0
  43. package/lib/model/exception.js +363 -0
  44. package/lib/model/http/device.js +215 -0
  45. package/lib/model/http/error-codes.js +121 -0
  46. package/lib/model/http/exception.js +151 -0
  47. package/lib/model/http/subdevice.js +133 -0
  48. package/lib/model/push/alarm.js +112 -0
  49. package/lib/model/push/bind.js +97 -0
  50. package/lib/model/push/common.js +282 -0
  51. package/lib/model/push/diffuser-light.js +100 -0
  52. package/lib/model/push/diffuser-spray.js +83 -0
  53. package/lib/model/push/factory.js +229 -0
  54. package/lib/model/push/generic.js +115 -0
  55. package/lib/model/push/hub-battery.js +59 -0
  56. package/lib/model/push/hub-mts100-all.js +64 -0
  57. package/lib/model/push/hub-mts100-mode.js +59 -0
  58. package/lib/model/push/hub-mts100-temperature.js +62 -0
  59. package/lib/model/push/hub-online.js +59 -0
  60. package/lib/model/push/hub-sensor-alert.js +61 -0
  61. package/lib/model/push/hub-sensor-all.js +59 -0
  62. package/lib/model/push/hub-sensor-smoke.js +110 -0
  63. package/lib/model/push/hub-sensor-temphum.js +62 -0
  64. package/lib/model/push/hub-subdevicelist.js +50 -0
  65. package/lib/model/push/hub-togglex.js +60 -0
  66. package/lib/model/push/index.js +81 -0
  67. package/lib/model/push/online.js +53 -0
  68. package/lib/model/push/presence-study.js +61 -0
  69. package/lib/model/push/sensor-latestx.js +106 -0
  70. package/lib/model/push/timerx.js +63 -0
  71. package/lib/model/push/togglex.js +78 -0
  72. package/lib/model/push/triggerx.js +62 -0
  73. package/lib/model/push/unbind.js +34 -0
  74. package/lib/model/push/water-leak.js +107 -0
  75. package/lib/model/states/diffuser-light-state.js +119 -0
  76. package/lib/model/states/diffuser-spray-state.js +58 -0
  77. package/lib/model/states/garage-door-state.js +71 -0
  78. package/lib/model/states/index.js +38 -0
  79. package/lib/model/states/light-state.js +134 -0
  80. package/lib/model/states/presence-sensor-state.js +239 -0
  81. package/lib/model/states/roller-shutter-state.js +82 -0
  82. package/lib/model/states/spray-state.js +58 -0
  83. package/lib/model/states/thermostat-state.js +297 -0
  84. package/lib/model/states/timer-state.js +192 -0
  85. package/lib/model/states/toggle-state.js +105 -0
  86. package/lib/model/states/trigger-state.js +155 -0
  87. package/lib/subscription.js +587 -0
  88. package/lib/utilities/conversion.js +62 -0
  89. package/lib/utilities/debug.js +165 -0
  90. package/lib/utilities/mqtt.js +152 -0
  91. package/lib/utilities/network.js +53 -0
  92. package/lib/utilities/options.js +64 -0
  93. package/lib/utilities/request-queue.js +161 -0
  94. package/lib/utilities/ssid.js +37 -0
  95. package/lib/utilities/state-changes.js +66 -0
  96. package/lib/utilities/stats.js +687 -0
  97. package/lib/utilities/timer.js +310 -0
  98. package/lib/utilities/trigger.js +286 -0
  99. package/package.json +73 -0
@@ -0,0 +1,687 @@
1
+ 'use strict';
2
+
3
+ const { parsePushNotification } = require('../../model/push');
4
+
5
+ /**
6
+ * Push notification namespace to data key mapping for hub features.
7
+ *
8
+ * Maps Meross push notification namespaces to their corresponding data keys in the notification payload.
9
+ */
10
+ const PUSH_MAP = {
11
+ // Basic hub functionality
12
+ 'Appliance.Hub.Online': 'online',
13
+ 'Appliance.Hub.ToggleX': 'togglex',
14
+ 'Appliance.Hub.Battery': 'battery',
15
+ 'Appliance.Hub.Sensor.WaterLeak': 'waterLeak',
16
+
17
+ // Sensor hub functionality
18
+ 'Appliance.Hub.Sensor.All': 'all',
19
+ 'Appliance.Hub.Sensor.TempHum': 'tempHum',
20
+ 'Appliance.Hub.Sensor.Alert': 'alert',
21
+ 'Appliance.Hub.Sensor.Smoke': 'smokeAlarm',
22
+ 'Appliance.Control.Sensor.LatestX': 'latest',
23
+
24
+ // MTS100 thermostat hub functionality
25
+ 'Appliance.Hub.Mts100.All': 'all',
26
+ 'Appliance.Hub.Mts100.Mode': 'mode',
27
+ 'Appliance.Hub.Mts100.Temperature': 'temperature'
28
+ };
29
+
30
+ /**
31
+ * Hub feature module.
32
+ * Provides functionality for hub devices including sensor management, MTS100 thermostat control,
33
+ * and automatic routing of push notifications to subdevices.
34
+ */
35
+ module.exports = {
36
+ /**
37
+ * Handles push notifications for hub functionality.
38
+ *
39
+ * Routes notifications to appropriate subdevices based on the namespace. This method is called
40
+ * automatically by the base device when push notifications are received.
41
+ *
42
+ * @param {string} namespace - The namespace of the push notification
43
+ * @param {Object} data - The push notification data
44
+ * @returns {boolean} True if the notification was handled locally, false otherwise
45
+ * @private
46
+ */
47
+ handlePushNotification(namespace, data) {
48
+ const dataKey = PUSH_MAP[namespace];
49
+
50
+ if (!dataKey) {
51
+ return false;
52
+ }
53
+
54
+ const payload = data[dataKey];
55
+ if (!payload) {
56
+ const logger = this.cloudInst?.options?.logger || console.warn;
57
+ logger(`${this.constructor.name} could not find ${dataKey} attribute in push notification data: ${JSON.stringify(data)}`);
58
+ return false;
59
+ }
60
+
61
+ const notification = parsePushNotification(namespace, data, this.uuid);
62
+ if (notification && typeof notification.routeToSubdevices === 'function') {
63
+ notification.routeToSubdevices(this);
64
+ }
65
+
66
+ return true;
67
+ },
68
+
69
+ /**
70
+ * Collects subdevice IDs, separating sensors from MTS100 thermostats.
71
+ *
72
+ * Uses registered subdevices if available, otherwise falls back to subDeviceList array.
73
+ *
74
+ * @returns {{sensorIds: string[], mts100Ids: string[]}} Object containing arrays of sensor and MTS100 IDs
75
+ * @private
76
+ */
77
+ _collectSubdeviceIds() {
78
+ const subdevices = this.getSubdevices();
79
+ const sensorIds = [];
80
+ const mts100Ids = [];
81
+
82
+ if (subdevices.length > 0) {
83
+ // Use registered subdevices
84
+ for (const sub of subdevices) {
85
+ if (sub.type === 'mts100v3') {
86
+ mts100Ids.push(sub.subdeviceId);
87
+ } else {
88
+ sensorIds.push(sub.subdeviceId);
89
+ }
90
+ }
91
+ } else if (this.subDeviceList && Array.isArray(this.subDeviceList) && this.subDeviceList.length > 0) {
92
+ // Fallback to old array-based approach if subdevices not yet registered
93
+ for (const sub of this.subDeviceList) {
94
+ const subType = sub.subDeviceType || sub.type;
95
+ const subId = sub.subDeviceId || sub.id;
96
+
97
+ if (subType === 'mts100v3') {
98
+ mts100Ids.push(subId);
99
+ } else {
100
+ sensorIds.push(subId);
101
+ }
102
+ }
103
+ }
104
+
105
+ return { sensorIds, mts100Ids };
106
+ },
107
+
108
+ /**
109
+ * Updates sensor subdevices by fetching sensor data, latest readings, and battery status.
110
+ *
111
+ * @param {string[]} sensorIds - Array of sensor subdevice IDs to update
112
+ * @returns {Promise<void>} Promise that resolves when sensor update is complete
113
+ * @private
114
+ */
115
+ async _updateSensorSubdevices(sensorIds) {
116
+ if (sensorIds.length === 0) {
117
+ return;
118
+ }
119
+
120
+ await this.getAllSensors(sensorIds);
121
+
122
+ try {
123
+ if (typeof this.getLatestHubSensorReadings === 'function') {
124
+ await this.getLatestHubSensorReadings(sensorIds, ['light', 'temp', 'humi']);
125
+ }
126
+ } catch (latestError) {
127
+ const logger = this.cloudInst?.options?.logger || console.debug;
128
+ logger(`Failed to fetch latest sensor readings: ${latestError.message}`);
129
+ }
130
+
131
+ try {
132
+ if (typeof this.getHubBattery === 'function') {
133
+ await this.getHubBattery();
134
+ }
135
+ } catch (batteryError) {
136
+ const logger = this.cloudInst?.options?.logger || console.debug;
137
+ logger(`Failed to update battery data: ${batteryError.message}`);
138
+ }
139
+ },
140
+
141
+ /**
142
+ * Updates MTS100 thermostat subdevices.
143
+ *
144
+ * @param {string[]} mts100Ids - Array of MTS100 subdevice IDs to update
145
+ * @returns {Promise<void>} Promise that resolves when MTS100 update is complete
146
+ * @private
147
+ */
148
+ async _updateMts100Subdevices(mts100Ids) {
149
+ if (mts100Ids.length === 0) {
150
+ return;
151
+ }
152
+
153
+ await this.getMts100All(mts100Ids);
154
+ },
155
+
156
+ /**
157
+ * Overrides refreshState to update hub subdevices.
158
+ *
159
+ * Calls the parent refreshState implementation and then updates all hub subdevices
160
+ * automatically (sensors and MTS100 thermostats).
161
+ *
162
+ * @returns {Promise<void>} Promise that resolves when state is refreshed
163
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
164
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
165
+ */
166
+ async refreshState() {
167
+ const { sensorIds, mts100Ids } = this._collectSubdeviceIds();
168
+
169
+ try {
170
+ await this._updateSensorSubdevices(sensorIds);
171
+ await this._updateMts100Subdevices(mts100Ids);
172
+ } catch (error) {
173
+ const logger = this.cloudInst?.options?.logger || console.error;
174
+ logger(`Error occurred during hub subdevice update: ${error.message}`);
175
+ }
176
+ },
177
+
178
+ // ===== Basic Hub Functionality =====
179
+
180
+ /**
181
+ * Gets the hub's battery status.
182
+ *
183
+ * Automatically routes battery data to the appropriate subdevices when the response is received.
184
+ *
185
+ * @returns {Promise<Object>} Promise that resolves with battery data containing `battery` array
186
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
187
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
188
+ */
189
+ async getHubBattery() {
190
+ const payload = { 'battery': [] };
191
+ const response = await this.publishMessage('GET', 'Appliance.Hub.Battery', payload, null);
192
+
193
+ if (response && response.battery && Array.isArray(response.battery)) {
194
+ for (const batteryData of response.battery) {
195
+ const subdeviceId = batteryData.id;
196
+ const subdevice = this.getSubdevice(subdeviceId);
197
+ if (subdevice && typeof subdevice.handleSubdeviceNotification === 'function') {
198
+ await subdevice.handleSubdeviceNotification('Appliance.Hub.Battery', batteryData);
199
+ }
200
+ }
201
+ }
202
+
203
+ return response;
204
+ },
205
+
206
+ /**
207
+ * Gets the hub's online status.
208
+ *
209
+ * @returns {Promise<Object>} Promise that resolves with online status data
210
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
211
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
212
+ */
213
+ async getHubOnline() {
214
+ return await this.publishMessage('GET', 'Appliance.Hub.Online', {});
215
+ },
216
+
217
+ /**
218
+ * Controls a hub toggleX subdevice (on/off).
219
+ *
220
+ * @param {string} subId - Subdevice ID
221
+ * @param {boolean} onoff - True to turn on, false to turn off
222
+ * @returns {Promise<Object>} Promise that resolves with response data
223
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
224
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
225
+ */
226
+ async setHubToggleX(subId, onoff) {
227
+ const payload = { 'togglex': [{ 'id': subId, 'onoff': onoff ? 1 : 0 }] };
228
+ return await this.publishMessage('SET', 'Appliance.Hub.ToggleX', payload);
229
+ },
230
+
231
+ /**
232
+ * Gets the hub's exception information.
233
+ *
234
+ * @returns {Promise<Object>} Promise that resolves with exception data
235
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
236
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
237
+ */
238
+ async getHubException() {
239
+ return await this.publishMessage('GET', 'Appliance.Hub.Exception', {});
240
+ },
241
+
242
+ /**
243
+ * Gets the hub's report information.
244
+ *
245
+ * @returns {Promise<Object>} Promise that resolves with report data
246
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
247
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
248
+ */
249
+ async getHubReport() {
250
+ return await this.publishMessage('GET', 'Appliance.Hub.Report', {});
251
+ },
252
+
253
+ /**
254
+ * Initiates pairing of a subdevice to the hub.
255
+ *
256
+ * @returns {Promise<Object>} Promise that resolves with response data
257
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
258
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
259
+ */
260
+ async setHubPairSubDev() {
261
+ return await this.publishMessage('SET', 'Appliance.Hub.PairSubDev', {});
262
+ },
263
+
264
+ /**
265
+ * Controls the beep/buzzer of a hub subdevice.
266
+ *
267
+ * @param {string|Array<string>} subIds - Subdevice ID(s) to control
268
+ * @param {boolean} onoff - True to turn on buzzer, false to turn off
269
+ * @returns {Promise<Object>} Promise that resolves with response data
270
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
271
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
272
+ */
273
+ async setHubSubDeviceBeep(subIds, onoff) {
274
+ const payload = { 'alarm': [] };
275
+ const ids = Array.isArray(subIds) ? subIds : [subIds];
276
+ ids.forEach(id => payload.alarm.push({ id, onoff: onoff ? 1 : 0 }));
277
+ return await this.publishMessage('SET', 'Appliance.Hub.SubDevice.Beep', payload);
278
+ },
279
+
280
+ /**
281
+ * Gets the beep/buzzer status of hub subdevices.
282
+ *
283
+ * @param {string|Array<string>} subIds - Subdevice ID(s) to query
284
+ * @returns {Promise<Object>} Promise that resolves with beep status data containing `alarm` array
285
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
286
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
287
+ */
288
+ async getHubSubDeviceBeep(subIds) {
289
+ const payload = { 'alarm': [] };
290
+ const ids = Array.isArray(subIds) ? subIds : [subIds];
291
+ ids.forEach(id => payload.alarm.push({ id }));
292
+ return await this.publishMessage('GET', 'Appliance.Hub.SubDevice.Beep', payload);
293
+ },
294
+
295
+ /**
296
+ * Gets the motor adjustment schedule for hub subdevices.
297
+ *
298
+ * @param {string|Array<string>} subIds - Subdevice ID(s) to query
299
+ * @returns {Promise<Object>} Promise that resolves with motor adjustment data containing `adjust` array
300
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
301
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
302
+ */
303
+ async getHubSubDeviceMotorAdjust(subIds) {
304
+ const payload = { 'adjust': [] };
305
+ const ids = Array.isArray(subIds) ? subIds : [subIds];
306
+ ids.forEach(id => payload.adjust.push({ id }));
307
+ return await this.publishMessage('GET', 'Appliance.Hub.SubDevice.MotorAdjust', payload);
308
+ },
309
+
310
+ /**
311
+ * Controls the motor adjustment schedule for hub subdevices.
312
+ *
313
+ * @param {Object|Array<Object>} adjustData - Motor adjustment data
314
+ * @param {string} [adjustData.id] - Subdevice ID
315
+ * @param {number} [adjustData.days] - Days for schedule
316
+ * @param {number} [adjustData.minutes] - Minutes for schedule
317
+ * @param {boolean} [adjustData.enable] - Enable/disable schedule
318
+ * @returns {Promise<Object>} Promise that resolves with response data
319
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
320
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
321
+ */
322
+ async setHubSubDeviceMotorAdjust(adjustData) {
323
+ const payload = { 'adjust': Array.isArray(adjustData) ? adjustData : [adjustData] };
324
+ return await this.publishMessage('SET', 'Appliance.Hub.SubDevice.MotorAdjust', payload);
325
+ },
326
+
327
+ /**
328
+ * Gets the version information for hub subdevices.
329
+ *
330
+ * @param {string|Array<string>} [subIds=[]] - Subdevice ID(s), empty array gets all
331
+ * @returns {Promise<Object>} Promise that resolves with version data containing `version` array
332
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
333
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
334
+ */
335
+ async getHubSubDeviceVersion(subIds = []) {
336
+ const payload = { 'version': [] };
337
+ if (Array.isArray(subIds) && subIds.length > 0) {
338
+ subIds.forEach(id => payload.version.push({ id }));
339
+ }
340
+ return await this.publishMessage('GET', 'Appliance.Hub.SubDevice.Version', payload);
341
+ },
342
+
343
+ // ===== Sensor Hub Functionality =====
344
+
345
+ /**
346
+ * Gets all sensor data for specified sensor IDs.
347
+ *
348
+ * @param {string|Array<string>} sensorIds - Single sensor ID or array of sensor IDs
349
+ * @returns {Promise<Object>} Promise that resolves with sensor data containing `all` array
350
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
351
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
352
+ */
353
+ async getAllSensors(sensorIds) {
354
+ const payload = { 'all': [] };
355
+ if (Array.isArray(sensorIds)) {
356
+ sensorIds.forEach(id => payload.all.push({ id }));
357
+ } else {
358
+ payload.all.push({ id: sensorIds });
359
+ }
360
+
361
+ const response = await this.publishMessage('GET', 'Appliance.Hub.Sensor.All', payload);
362
+
363
+ if (response && response.all && Array.isArray(response.all)) {
364
+ for (const sensorData of response.all) {
365
+ const subdeviceId = sensorData.id;
366
+ const subdevice = this.getSubdevice(subdeviceId);
367
+ if (subdevice && typeof subdevice.handleSubdeviceNotification === 'function') {
368
+ await subdevice.handleSubdeviceNotification('Appliance.Hub.Sensor.All', sensorData);
369
+ }
370
+ }
371
+ }
372
+
373
+ return response;
374
+ },
375
+
376
+ /**
377
+ * Gets latest sensor readings (temperature, humidity, and light/lux) for specified sensor IDs.
378
+ *
379
+ * This method fetches the most recent readings including lux data which is not available
380
+ * in getAllSensors. The data is automatically routed to the appropriate subdevices.
381
+ *
382
+ * @param {string|Array<string>} sensorIds - Single sensor ID or array of sensor IDs
383
+ * @param {Array<string>} [dataTypes=['light', 'temp', 'humi']] - Array of data types to request
384
+ * @returns {Promise<Object>} Promise that resolves with latest sensor data containing `latest` array
385
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
386
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
387
+ */
388
+ async getLatestHubSensorReadings(sensorIds, dataTypes = ['light', 'temp', 'humi']) {
389
+ const payload = { 'latest': [] };
390
+ const sensorIdArray = Array.isArray(sensorIds) ? sensorIds : [sensorIds];
391
+
392
+ sensorIdArray.forEach(subId => {
393
+ payload.latest.push({
394
+ subId,
395
+ channel: 0,
396
+ data: dataTypes
397
+ });
398
+ });
399
+
400
+ const response = await this.publishMessage('GET', 'Appliance.Control.Sensor.LatestX', payload, null);
401
+
402
+ if (response && response.latest && Array.isArray(response.latest)) {
403
+ for (const latestData of response.latest) {
404
+ const subdeviceId = latestData.subId;
405
+ const subdevice = this.getSubdevice(subdeviceId);
406
+ if (subdevice && typeof subdevice.handleSubdeviceNotification === 'function') {
407
+ await subdevice.handleSubdeviceNotification('Appliance.Control.Sensor.LatestX', latestData);
408
+ }
409
+ }
410
+ }
411
+
412
+ return response;
413
+ },
414
+
415
+ /**
416
+ * Gets temperature and humidity sensor data for specified sensor IDs.
417
+ *
418
+ * @param {string|Array<string>} sensorIds - Single sensor ID or array of sensor IDs
419
+ * @returns {Promise<Object>} Promise that resolves with temperature/humidity data containing `tempHum` array
420
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
421
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
422
+ */
423
+ async getTempHumSensor(sensorIds) {
424
+ const payload = { 'tempHum': [] };
425
+ if (Array.isArray(sensorIds)) {
426
+ sensorIds.forEach(id => payload.tempHum.push({ id }));
427
+ } else {
428
+ payload.tempHum.push({ id: sensorIds });
429
+ }
430
+ return await this.publishMessage('GET', 'Appliance.Hub.Sensor.TempHum', payload);
431
+ },
432
+
433
+ /**
434
+ * Gets alert sensor data for specified sensor IDs.
435
+ *
436
+ * @param {string|Array<string>} sensorIds - Single sensor ID or array of sensor IDs
437
+ * @returns {Promise<Object>} Promise that resolves with alert sensor data containing `alert` array
438
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
439
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
440
+ */
441
+ async getAlertSensor(sensorIds) {
442
+ const payload = { 'alert': [] };
443
+ if (Array.isArray(sensorIds)) {
444
+ sensorIds.forEach(id => payload.alert.push({ id }));
445
+ } else {
446
+ payload.alert.push({ id: sensorIds });
447
+ }
448
+ return await this.publishMessage('GET', 'Appliance.Hub.Sensor.Alert', payload);
449
+ },
450
+
451
+ /**
452
+ * Gets smoke alarm status for specified smoke detector IDs.
453
+ *
454
+ * The data is automatically routed to the appropriate subdevices.
455
+ *
456
+ * @param {string|Array<string>} sensorIds - Single sensor ID or array of sensor IDs
457
+ * @returns {Promise<Object>} Promise that resolves with smoke alarm status data containing `smokeAlarm` array
458
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
459
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
460
+ */
461
+ async getSmokeAlarmStatus(sensorIds) {
462
+ const payload = { 'smokeAlarm': [] };
463
+ if (Array.isArray(sensorIds)) {
464
+ sensorIds.forEach(id => payload.smokeAlarm.push({ id }));
465
+ } else {
466
+ payload.smokeAlarm.push({ id: sensorIds });
467
+ }
468
+
469
+ const response = await this.publishMessage('GET', 'Appliance.Hub.Sensor.Smoke', payload);
470
+
471
+ if (response && response.smokeAlarm && Array.isArray(response.smokeAlarm)) {
472
+ for (const smokeData of response.smokeAlarm) {
473
+ const subdeviceId = smokeData.id;
474
+ const subdevice = this.getSubdevice(subdeviceId);
475
+ if (subdevice && typeof subdevice.handleSubdeviceNotification === 'function') {
476
+ await subdevice.handleSubdeviceNotification('Appliance.Hub.Sensor.Smoke', smokeData);
477
+ }
478
+ }
479
+ }
480
+
481
+ return response;
482
+ },
483
+
484
+ /**
485
+ * Gets water leak sensor data for specified sensor IDs.
486
+ *
487
+ * @param {string|Array<string>} sensorIds - Single sensor ID or array of sensor IDs
488
+ * @returns {Promise<Object>} Promise that resolves with water leak sensor data containing `waterleak` array
489
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
490
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
491
+ */
492
+ async getWaterLeakSensor(sensorIds) {
493
+ const payload = { 'waterleak': [] };
494
+ if (Array.isArray(sensorIds)) {
495
+ sensorIds.forEach(id => payload.waterleak.push({ id }));
496
+ } else {
497
+ payload.waterleak.push({ id: sensorIds });
498
+ }
499
+ return await this.publishMessage('GET', 'Appliance.Hub.Sensor.WaterLeak', payload);
500
+ },
501
+
502
+ /**
503
+ * Gets sensor adjustment (calibration) settings for specified sensor IDs.
504
+ *
505
+ * @param {string|Array<string>} [sensorIds=[]] - Single sensor ID or array of sensor IDs, empty array gets all
506
+ * @returns {Promise<Object>} Promise that resolves with sensor adjustment data containing `adjust` array
507
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
508
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
509
+ */
510
+ async getHubSensorAdjust(sensorIds = []) {
511
+ const payload = { 'adjust': [] };
512
+ if (Array.isArray(sensorIds) && sensorIds.length > 0) {
513
+ sensorIds.forEach(id => payload.adjust.push({ id }));
514
+ }
515
+ return await this.publishMessage('GET', 'Appliance.Hub.Sensor.Adjust', payload);
516
+ },
517
+
518
+ /**
519
+ * Controls (sets) sensor adjustment (calibration) settings.
520
+ *
521
+ * @param {Object|Array<Object>} adjustData - Sensor adjustment data
522
+ * @param {string} [adjustData.id] - Sensor ID
523
+ * @param {number} [adjustData.temperature] - Temperature adjustment offset
524
+ * @param {number} [adjustData.humidity] - Humidity adjustment offset
525
+ * @returns {Promise<Object>} Promise that resolves with response data
526
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
527
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
528
+ */
529
+ async setHubSensorAdjust(adjustData) {
530
+ const payload = { 'adjust': Array.isArray(adjustData) ? adjustData : [adjustData] };
531
+ return await this.publishMessage('SET', 'Appliance.Hub.Sensor.Adjust', payload);
532
+ },
533
+
534
+ /**
535
+ * Gets door/window sensor data for specified sensor IDs.
536
+ *
537
+ * @param {string|Array<string>} [sensorIds=[]] - Single sensor ID or array of sensor IDs, empty array gets all (max 16)
538
+ * @returns {Promise<Object>} Promise that resolves with door/window sensor data containing `doorWindow` array
539
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
540
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
541
+ */
542
+ async getHubSensorDoorWindow(sensorIds = []) {
543
+ const payload = { 'doorWindow': [] };
544
+ if (Array.isArray(sensorIds) && sensorIds.length > 0) {
545
+ sensorIds.forEach(id => payload.doorWindow.push({ id }));
546
+ }
547
+ return await this.publishMessage('GET', 'Appliance.Hub.Sensor.DoorWindow', payload);
548
+ },
549
+
550
+ /**
551
+ * Controls (sets) door/window sensor synchronization (if supported).
552
+ *
553
+ * Note: This namespace primarily supports GET and PUSH, SET may not be available for all devices.
554
+ *
555
+ * @param {Object|Array<Object>} doorWindowData - Door/window data
556
+ * @returns {Promise<Object>} Promise that resolves with response data
557
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
558
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
559
+ */
560
+ async setHubSensorDoorWindow(doorWindowData) {
561
+ const payload = { 'doorWindow': Array.isArray(doorWindowData) ? doorWindowData : [doorWindowData] };
562
+ return await this.publishMessage('SET', 'Appliance.Hub.Sensor.DoorWindow', payload);
563
+ },
564
+
565
+ // ===== MTS100 Thermostat Hub Functionality =====
566
+
567
+ /**
568
+ * Gets MTS100 thermostat valve data for specified IDs.
569
+ *
570
+ * @param {Array<string>} ids - Array of MTS100 subdevice IDs
571
+ * @returns {Promise<Object>} Promise that resolves with MTS100 data containing `all` array
572
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
573
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
574
+ */
575
+ async getMts100All(ids) {
576
+ const payload = { 'all': [] };
577
+ ids.forEach(id => payload.all.push({ id }));
578
+ return await this.publishMessage('GET', 'Appliance.Hub.Mts100.All', payload, null);
579
+ },
580
+
581
+ /**
582
+ * Controls MTS100 thermostat mode.
583
+ *
584
+ * @param {string} subId - MTS100 subdevice ID
585
+ * @param {number|import('../lib/enums').ThermostatMode} mode - Mode value from ThermostatMode enum
586
+ * @returns {Promise<Object>} Promise that resolves with response data
587
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
588
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
589
+ */
590
+ async setHubMts100Mode(subId, mode) {
591
+ const payload = { 'mode': [{ 'id': subId, 'state': mode }] };
592
+ return await this.publishMessage('SET', 'Appliance.Hub.Mts100.Mode', payload);
593
+ },
594
+
595
+ /**
596
+ * Controls MTS100 thermostat temperature settings.
597
+ *
598
+ * Mutates the temp object by adding the subId property before sending the command.
599
+ *
600
+ * @param {string} subId - MTS100 subdevice ID
601
+ * @param {Object} temp - Temperature object (will be mutated with subId)
602
+ * @param {number} [temp.temperature] - Target temperature
603
+ * @param {number} [temp.min] - Minimum temperature
604
+ * @param {number} [temp.max] - Maximum temperature * @returns {Promise<Object>} Promise that resolves with response data
605
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
606
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
607
+ */
608
+ async setHubMts100Temperature(subId, temp) {
609
+ temp.id = subId;
610
+ const payload = { 'temperature': [temp] };
611
+ return await this.publishMessage('SET', 'Appliance.Hub.Mts100.Temperature', payload);
612
+ },
613
+
614
+ /**
615
+ * Controls MTS100 thermostat adjustment settings.
616
+ *
617
+ * Mutates the adjustData object by adding the subId property before sending the command.
618
+ *
619
+ * @param {string} subId - MTS100 subdevice ID
620
+ * @param {Object} adjustData - Adjustment data object (will be mutated with subId)
621
+ * @returns {Promise<Object>} Promise that resolves with response data
622
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
623
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
624
+ */
625
+ async setHubMts100Adjust(subId, adjustData) {
626
+ adjustData.id = subId;
627
+ const payload = { 'adjust': [adjustData] };
628
+ return await this.publishMessage('SET', 'Appliance.Hub.Mts100.Adjust', payload);
629
+ },
630
+
631
+ /**
632
+ * Gets MTS100 adjustment settings for specified IDs.
633
+ *
634
+ * @param {Array<string>} ids - Array of MTS100 subdevice IDs
635
+ * @returns {Promise<Object>} Promise that resolves with adjustment data containing `adjust` array
636
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
637
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
638
+ */
639
+ async getMts100Adjust(ids) {
640
+ const payload = { 'adjust': [] };
641
+ ids.forEach(id => payload.adjust.push({ id }));
642
+ return await this.publishMessage('GET', 'Appliance.Hub.Mts100.Adjust', payload);
643
+ },
644
+
645
+ /**
646
+ * Gets MTS100 super control data for specified IDs.
647
+ *
648
+ * @param {Array<string>} ids - Array of MTS100 subdevice IDs
649
+ * @returns {Promise<Object>} Promise that resolves with super control data containing `superCtl` array
650
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
651
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
652
+ */
653
+ async getMts100SuperCtl(ids) {
654
+ const payload = { 'superCtl': [] };
655
+ ids.forEach(id => payload.superCtl.push({ id }));
656
+ return await this.publishMessage('GET', 'Appliance.Hub.Mts100.SuperCtl', payload);
657
+ },
658
+
659
+ /**
660
+ * Gets MTS100 schedule B data for specified IDs.
661
+ *
662
+ * @param {Array<string>} ids - Array of MTS100 subdevice IDs
663
+ * @returns {Promise<Object>} Promise that resolves with schedule B data containing `scheduleB` array
664
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
665
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
666
+ */
667
+ async getMts100ScheduleB(ids) {
668
+ const payload = { 'scheduleB': [] };
669
+ ids.forEach(id => payload.scheduleB.push({ id }));
670
+ return await this.publishMessage('GET', 'Appliance.Hub.Mts100.ScheduleB', payload);
671
+ },
672
+
673
+ /**
674
+ * Gets MTS100 configuration for specified IDs.
675
+ *
676
+ * @param {Array<string>} ids - Array of MTS100 subdevice IDs
677
+ * @returns {Promise<Object>} Promise that resolves with configuration data containing `config` array
678
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
679
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
680
+ */
681
+ async getMts100Config(ids) {
682
+ const payload = { 'config': [] };
683
+ ids.forEach(id => payload.config.push({ id }));
684
+ return await this.publishMessage('GET', 'Appliance.Hub.Mts100.Config', payload);
685
+ }
686
+ };
687
+