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,297 @@
1
+ 'use strict';
2
+
3
+ const PresenceSensorState = require('../../model/states/presence-sensor-state');
4
+ const { normalizeChannel } = require('../../utilities/options');
5
+ const { buildStateChanges } = require('../../utilities/state-changes');
6
+
7
+ /**
8
+ * Presence sensor feature module.
9
+ * Provides access to presence detection and light sensor data for devices that support it.
10
+ */
11
+ module.exports = {
12
+ /**
13
+ * Updates internal presence sensor state from LatestX notification data.
14
+ *
15
+ * Called automatically when LatestX push notifications are received or responses are processed.
16
+ * Extracts presence and light data from the notification and updates the cached state.
17
+ *
18
+ * @param {Object|Array} latestData - Latest sensor readings (single object or array)
19
+ * @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
20
+ * @private
21
+ */
22
+ _updatePresenceState(latestData, source = 'response') {
23
+ if (!latestData) {return;}
24
+
25
+ const latestArray = Array.isArray(latestData) ? latestData : [latestData];
26
+
27
+ for (const entry of latestArray) {
28
+ if (!entry || !entry.data) {
29
+ continue;
30
+ }
31
+
32
+ const channel = entry.channel !== undefined ? entry.channel : 0;
33
+
34
+ const oldState = this._presenceSensorStateByChannel.get(channel);
35
+ const oldValue = oldState ? {
36
+ isPresent: oldState.isPresent,
37
+ distance: oldState.distanceRaw,
38
+ light: oldState.lightLux
39
+ } : undefined;
40
+
41
+ let state = this._presenceSensorStateByChannel.get(channel);
42
+ if (!state) {
43
+ state = new PresenceSensorState({ channel });
44
+ this._presenceSensorStateByChannel.set(channel, state);
45
+ }
46
+
47
+ const stateUpdate = { channel };
48
+
49
+ if (entry.data.presence && Array.isArray(entry.data.presence) && entry.data.presence.length > 0) {
50
+ const presenceData = entry.data.presence[0];
51
+ stateUpdate.presence = {
52
+ value: presenceData.value,
53
+ distance: presenceData.distance,
54
+ timestamp: presenceData.timestamp,
55
+ times: presenceData.times
56
+ };
57
+ }
58
+
59
+ if (entry.data.light && Array.isArray(entry.data.light) && entry.data.light.length > 0) {
60
+ const lightData = entry.data.light[0];
61
+ stateUpdate.light = {
62
+ value: lightData.value,
63
+ timestamp: lightData.timestamp
64
+ };
65
+ }
66
+
67
+ state.update(stateUpdate);
68
+
69
+ const newValue = buildStateChanges(oldValue, {
70
+ isPresent: state.isPresent,
71
+ distance: state.distanceRaw,
72
+ light: state.lightLux
73
+ });
74
+
75
+ if (Object.keys(newValue).length > 0) {
76
+ this.emit('stateChange', {
77
+ type: 'presence',
78
+ channel,
79
+ value: newValue,
80
+ oldValue,
81
+ source,
82
+ timestamp: Date.now()
83
+ });
84
+ }
85
+ }
86
+ },
87
+
88
+ /**
89
+ * Gets the cached presence sensor state for a channel.
90
+ *
91
+ * Returns the cached state without making a request to the device. Use {@link getLatestSensorReadings}
92
+ * to fetch fresh data from the device.
93
+ *
94
+ * @param {number} [channel=0] - Channel to get state for (default: 0)
95
+ * @returns {import('../lib/model/states/presence-sensor-state').PresenceSensorState|null} Presence sensor state object or null if no cached state
96
+ * @throws {Error} If state has not been initialized (call refreshState() first)
97
+ */
98
+ getCachedPresenceSensorState(channel = 0) {
99
+ this.validateState();
100
+ return this._presenceSensorStateByChannel.get(channel);
101
+ },
102
+
103
+ /**
104
+ * Gets the latest presence detection data.
105
+ *
106
+ * Returns formatted presence data from cached state. Returns null if no presence data
107
+ * is available for the channel.
108
+ *
109
+ * @param {number} [channel=0] - Channel to get presence for (default: 0)
110
+ * @returns {Object|null} Presence data object with value, isPresent (boolean), state ('presence'|'absence'), distance (in meters), distanceRaw (in mm), timestamp, and times, or null if no data
111
+ * @throws {Error} If state has not been initialized (call refreshState() first)
112
+ */
113
+ getPresence(channel = 0) {
114
+ const state = this.getCachedPresenceSensorState(channel);
115
+ if (!state || state.presenceValue === undefined) {
116
+ return null;
117
+ }
118
+ return {
119
+ value: state.presenceValue,
120
+ isPresent: state.isPresent,
121
+ state: state.presenceState,
122
+ distance: state.distanceMeters,
123
+ distanceRaw: state.distanceRaw,
124
+ timestamp: state.presenceTimestamp,
125
+ times: state.presenceTimes
126
+ };
127
+ },
128
+
129
+ /**
130
+ * Checks if presence is currently detected.
131
+ *
132
+ * @param {number} [channel=0] - Channel to check (default: 0)
133
+ * @returns {boolean|null} True if presence detected, false if absence detected, null if no data
134
+ * @throws {Error} If state has not been initialized (call refreshState() first)
135
+ */
136
+ isPresent(channel = 0) {
137
+ const state = this.getCachedPresenceSensorState(channel);
138
+ return state ? state.isPresent : null;
139
+ },
140
+
141
+ /**
142
+ * Gets the latest light/illuminance reading.
143
+ *
144
+ * Returns formatted light data from cached state. Returns null if no light data
145
+ * is available for the channel.
146
+ *
147
+ * @param {number} [channel=0] - Channel to get light for (default: 0)
148
+ * @returns {Object|null} Light data object with value and timestamp, or null if no data
149
+ * @throws {Error} If state has not been initialized (call refreshState() first)
150
+ */
151
+ getLight(channel = 0) {
152
+ const state = this.getCachedPresenceSensorState(channel);
153
+ if (!state || state.lightLux === undefined) {
154
+ return null;
155
+ }
156
+ return {
157
+ value: state.lightLux,
158
+ timestamp: state.lightTimestamp
159
+ };
160
+ },
161
+
162
+ /**
163
+ * Gets all sensor readings (presence and light).
164
+ *
165
+ * Returns both presence and light data from cached state in a single object.
166
+ *
167
+ * @param {number} [channel=0] - Channel to get readings for (default: 0)
168
+ * @returns {Object} Object containing all sensor readings with presence and light properties
169
+ * @throws {Error} If state has not been initialized (call refreshState() first)
170
+ */
171
+ getAllSensorReadings(channel = 0) {
172
+ const state = this.getCachedPresenceSensorState(channel);
173
+ return {
174
+ presence: state ? {
175
+ value: state.presenceValue,
176
+ isPresent: state.isPresent,
177
+ state: state.presenceState,
178
+ distance: state.distanceMeters,
179
+ distanceRaw: state.distanceRaw,
180
+ timestamp: state.presenceTimestamp,
181
+ times: state.presenceTimes
182
+ } : null,
183
+ light: state ? {
184
+ value: state.lightLux,
185
+ timestamp: state.lightTimestamp
186
+ } : null
187
+ };
188
+ },
189
+
190
+ /**
191
+ * Gets latest sensor readings from the device.
192
+ *
193
+ * Queries the device for the most recent sensor readings. Automatically updates the cached
194
+ * state when the response is received.
195
+ *
196
+ * @param {Object} [options={}] - Get options
197
+ * @param {Array<string>} [options.dataTypes=['presence', 'light']] - Array of data types to request
198
+ * @returns {Promise<Object>} Promise that resolves with latest sensor data containing `latest` array
199
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
200
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
201
+ */
202
+ async getLatestSensorReadings(options = {}) {
203
+ const dataTypes = options.dataTypes || ['presence', 'light'];
204
+ const payload = {
205
+ latest: [{
206
+ channel: 0,
207
+ data: dataTypes
208
+ }]
209
+ };
210
+
211
+ const response = await this.publishMessage('GET', 'Appliance.Control.Sensor.LatestX', payload);
212
+
213
+ if (response && response.latest) {
214
+ this._updatePresenceState(response.latest, 'response');
215
+ this._lastFullUpdateTimestamp = Date.now();
216
+ }
217
+
218
+ return response;
219
+ },
220
+
221
+ /**
222
+ * Gets presence configuration from the device.
223
+ *
224
+ * @param {Object} [options={}] - Get options
225
+ * @param {number} [options.channel=0] - Channel to get config for (default: 0)
226
+ * @returns {Promise<Object>} Promise that resolves with presence configuration containing `config` array
227
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
228
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
229
+ */
230
+ async getPresenceConfig(options = {}) {
231
+ const channel = normalizeChannel(options);
232
+ const payload = {
233
+ config: [{
234
+ channel
235
+ }]
236
+ };
237
+ return await this.publishMessage('GET', 'Appliance.Control.Presence.Config', payload);
238
+ },
239
+
240
+ /**
241
+ * Controls the presence sensor configuration.
242
+ *
243
+ * @param {Object|Array<Object>} configData - Config data object or array of config items
244
+ * @param {number} [configData.channel] - Channel to configure (default: 0)
245
+ * @param {Object} [configData.mode] - Mode configuration
246
+ * @param {number} [configData.mode.workMode] - Work mode (0=Unknown, 1=Biological detection only, 2=Security)
247
+ * @param {number} [configData.mode.testMode] - Test mode value
248
+ * @param {Object} [configData.noBodyTime] - No body detection time configuration
249
+ * @param {number} [configData.noBodyTime.time] - Time in seconds before absence is detected
250
+ * @param {Object} [configData.distance] - Distance configuration
251
+ * @param {number} [configData.distance.value] - Distance threshold in millimeters
252
+ * @param {Object} [configData.sensitivity] - Sensitivity configuration
253
+ * @param {number} [configData.sensitivity.level] - Sensitivity level (1=Anti-Interference, 2=Balance, 3=Responsive)
254
+ * @param {Object} [configData.mthx] - Motion threshold configuration
255
+ * @param {number} [configData.mthx.mth1] - Motion threshold 1
256
+ * @param {number} [configData.mthx.mth2] - Motion threshold 2
257
+ * @param {number} [configData.mthx.mth3] - Motion threshold 3
258
+ * @returns {Promise<Object>} Response from the device
259
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
260
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
261
+ */
262
+ async setPresenceConfig(configData) {
263
+ const payload = { config: Array.isArray(configData) ? configData : [configData] };
264
+ return await this.publishMessage('SET', 'Appliance.Control.Presence.Config', payload);
265
+ },
266
+
267
+ /**
268
+ * Gets presence study/calibration status from the device.
269
+ *
270
+ * @returns {Promise<Object>} Promise that resolves with presence study data
271
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
272
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
273
+ */
274
+ async getPresenceStudy() {
275
+ return await this.publishMessage('GET', 'Appliance.Control.Presence.Study', {});
276
+ },
277
+
278
+ /**
279
+ * Controls the presence study/calibration mode.
280
+ *
281
+ * Used to start or stop the presence sensor study/calibration process, which helps
282
+ * the device learn its environment for better detection accuracy.
283
+ *
284
+ * @param {Object|Array<Object>} studyData - Study data object or array of study items
285
+ * @param {number} [studyData.channel] - Channel to configure (default: 0)
286
+ * @param {number} [studyData.value] - Study mode value (typically 1-3)
287
+ * @param {number} [studyData.status] - Study status (0 = stop/inactive, 1 = start/active)
288
+ * @returns {Promise<Object>} Response from the device
289
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
290
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
291
+ */
292
+ async setPresenceStudy(studyData) {
293
+ const payload = { study: Array.isArray(studyData) ? studyData : [studyData] };
294
+ return await this.publishMessage('SET', 'Appliance.Control.Presence.Study', payload);
295
+ }
296
+ };
297
+