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,411 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DiffuserLightState = require('../../model/states/diffuser-light-state');
|
|
4
|
+
const DiffuserSprayState = require('../../model/states/diffuser-spray-state');
|
|
5
|
+
const { DiffuserLightMode, DiffuserSprayMode } = require('../../model/enums');
|
|
6
|
+
const { buildStateChanges } = require('../../utilities/state-changes');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Diffuser feature module.
|
|
10
|
+
* Provides control over diffuser light and spray functionality for essential oil diffusers.
|
|
11
|
+
*/
|
|
12
|
+
module.exports = {
|
|
13
|
+
/**
|
|
14
|
+
* Controls the diffuser light settings.
|
|
15
|
+
*
|
|
16
|
+
* Automatically includes the device UUID in the light configuration object.
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} options - Diffuser light options
|
|
19
|
+
* @param {Object} options.light - Light configuration object
|
|
20
|
+
* @param {number} [options.light.channel] - Channel to control (default: 0)
|
|
21
|
+
* @param {number} [options.light.onoff] - Turn light on (1) or off (0)
|
|
22
|
+
* @param {number} [options.light.mode] - Light mode (use DiffuserLightMode enum)
|
|
23
|
+
* @param {number} [options.light.luminance] - Brightness value
|
|
24
|
+
* @param {number} [options.light.rgb] - RGB color value
|
|
25
|
+
* @returns {Promise<Object>} Response from the device containing the updated light state
|
|
26
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
27
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
28
|
+
*/
|
|
29
|
+
async setDiffuserLight(options = {}) {
|
|
30
|
+
if (!options.light) {
|
|
31
|
+
throw new Error('light is required');
|
|
32
|
+
}
|
|
33
|
+
const light = { ...options.light };
|
|
34
|
+
light.uuid = this.uuid;
|
|
35
|
+
const payload = { 'light': [light] };
|
|
36
|
+
const response = await this.publishMessage('SET', 'Appliance.Control.Diffuser.Light', payload);
|
|
37
|
+
|
|
38
|
+
if (response && response.light) {
|
|
39
|
+
this._updateDiffuserLightState(response.light);
|
|
40
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
41
|
+
} else if (light) {
|
|
42
|
+
this._updateDiffuserLightState([light]);
|
|
43
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return response;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Controls the diffuser spray mode.
|
|
51
|
+
*
|
|
52
|
+
* Automatically includes the device UUID in the spray configuration.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} options - Diffuser spray options
|
|
55
|
+
* @param {number} [options.channel=0] - Channel to control (default: 0)
|
|
56
|
+
* @param {number|import('../lib/enums').DiffuserSprayMode} options.mode - Spray mode value or DiffuserSprayMode enum
|
|
57
|
+
* @returns {Promise<Object>} Response from the device containing the updated spray state
|
|
58
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
59
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
60
|
+
*/
|
|
61
|
+
async setDiffuserSpray(options = {}) {
|
|
62
|
+
const { normalizeChannel } = require('../../utilities/options');
|
|
63
|
+
if (options.mode === undefined) {
|
|
64
|
+
throw new Error('mode is required');
|
|
65
|
+
}
|
|
66
|
+
const channel = normalizeChannel(options);
|
|
67
|
+
const payload = { 'spray': [{ channel, 'mode': options.mode || 0, 'uuid': this.uuid }] };
|
|
68
|
+
const response = await this.publishMessage('SET', 'Appliance.Control.Diffuser.Spray', payload);
|
|
69
|
+
|
|
70
|
+
if (response && response.spray) {
|
|
71
|
+
this._updateDiffuserSprayState(response.spray, 'response');
|
|
72
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
73
|
+
} else {
|
|
74
|
+
this._updateDiffuserSprayState([{ channel, mode: options.mode || 0 }], 'response');
|
|
75
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return response;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Gets the current diffuser light state from the device.
|
|
83
|
+
*
|
|
84
|
+
* Use {@link getCachedDiffuserLightState} to get cached state without making a request.
|
|
85
|
+
* @param {Object} [options={}] - Get options
|
|
86
|
+
* @returns {Promise<Object>} Response containing light state with `light` array
|
|
87
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
88
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
89
|
+
*/
|
|
90
|
+
async getDiffuserLightState(_options = {}) {
|
|
91
|
+
const response = await this.publishMessage('GET', 'Appliance.Control.Diffuser.Light', {});
|
|
92
|
+
if (response && response.light) {
|
|
93
|
+
this._updateDiffuserLightState(response.light, 'response');
|
|
94
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
95
|
+
}
|
|
96
|
+
return response;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Gets the current diffuser spray state from the device.
|
|
101
|
+
*
|
|
102
|
+
* Use {@link getCachedDiffuserSprayState} to get cached state without making a request.
|
|
103
|
+
* @param {Object} [options={}] - Get options
|
|
104
|
+
* @returns {Promise<Object>} Response containing spray state with `spray` array
|
|
105
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
106
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
107
|
+
*/
|
|
108
|
+
async getDiffuserSprayState(_options = {}) {
|
|
109
|
+
const response = await this.publishMessage('GET', 'Appliance.Control.Diffuser.Spray', {});
|
|
110
|
+
if (response && response.spray) {
|
|
111
|
+
this._updateDiffuserSprayState(response.spray, 'response');
|
|
112
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
113
|
+
}
|
|
114
|
+
return response;
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Gets the cached diffuser light state for the specified channel.
|
|
119
|
+
*
|
|
120
|
+
* Returns cached state without making a request. Use {@link getDiffuserLightState} to fetch
|
|
121
|
+
* fresh state from the device. State is automatically updated when commands are sent or
|
|
122
|
+
* push notifications are received.
|
|
123
|
+
*
|
|
124
|
+
* @param {number} [channel=0] - Channel to get state for (default: 0)
|
|
125
|
+
* @returns {import('../lib/model/states/diffuser-light-state').DiffuserLightState|undefined} Cached light state or undefined if not available
|
|
126
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
127
|
+
*/
|
|
128
|
+
getCachedDiffuserLightState(channel = 0) {
|
|
129
|
+
this.validateState();
|
|
130
|
+
return this._diffuserLightStateByChannel.get(channel);
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Gets the diffuser light mode for the specified channel (cached).
|
|
135
|
+
*
|
|
136
|
+
* Returns the light mode enum from cached state. Use {@link getRawDiffuserLightMode} to get
|
|
137
|
+
* the raw numeric value.
|
|
138
|
+
*
|
|
139
|
+
* @param {number} [channel=0] - Channel to get mode for (default: 0)
|
|
140
|
+
* @returns {import('../lib/enums').DiffuserLightMode|undefined} DiffuserLightMode enum object (e.g., DiffuserLightMode.FIXED_RGB) or undefined if not available
|
|
141
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
142
|
+
* @see getRawDiffuserLightMode
|
|
143
|
+
*/
|
|
144
|
+
getDiffuserLightMode(channel = 0) {
|
|
145
|
+
this.validateState();
|
|
146
|
+
const lightState = this._diffuserLightStateByChannel.get(channel);
|
|
147
|
+
if (lightState && lightState.mode !== undefined && lightState.mode !== null) {
|
|
148
|
+
const enumKey = Object.keys(DiffuserLightMode).find(key => DiffuserLightMode[key] === lightState.mode);
|
|
149
|
+
return enumKey ? DiffuserLightMode[enumKey] : undefined;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Gets the raw numeric diffuser light mode value for the specified channel (cached).
|
|
156
|
+
*
|
|
157
|
+
* Returns the raw numeric mode value. For enum object, use {@link getDiffuserLightMode} instead.
|
|
158
|
+
*
|
|
159
|
+
* @param {number} [channel=0] - Channel to get mode for (default: 0)
|
|
160
|
+
* @returns {number|undefined} Raw numeric mode value or undefined if not available
|
|
161
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
162
|
+
* @see getDiffuserLightMode
|
|
163
|
+
*/
|
|
164
|
+
getRawDiffuserLightMode(channel = 0) {
|
|
165
|
+
this.validateState();
|
|
166
|
+
const lightState = this._diffuserLightStateByChannel.get(channel);
|
|
167
|
+
if (lightState) {
|
|
168
|
+
return lightState.mode;
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Gets the diffuser light brightness for the specified channel (cached).
|
|
175
|
+
*
|
|
176
|
+
* @param {number} [channel=0] - Channel to get brightness for (default: 0)
|
|
177
|
+
* @returns {number|undefined} Brightness value (0-100) or undefined if not available
|
|
178
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
179
|
+
*/
|
|
180
|
+
getDiffuserLightBrightness(channel = 0) {
|
|
181
|
+
this.validateState();
|
|
182
|
+
const lightState = this._diffuserLightStateByChannel.get(channel);
|
|
183
|
+
if (lightState) {
|
|
184
|
+
return lightState.luminance;
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Gets the diffuser light RGB color for the specified channel (cached).
|
|
191
|
+
*
|
|
192
|
+
* @param {number} [channel=0] - Channel to get color for (default: 0)
|
|
193
|
+
* @returns {Array<number>|undefined} RGB tuple [r, g, b] where each value is 0-255, or undefined if not available
|
|
194
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
195
|
+
*/
|
|
196
|
+
getDiffuserLightRgbColor(channel = 0) {
|
|
197
|
+
this.validateState();
|
|
198
|
+
const lightState = this._diffuserLightStateByChannel.get(channel);
|
|
199
|
+
if (lightState) {
|
|
200
|
+
return lightState.rgbTuple;
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Checks if the diffuser light is on for the specified channel (cached).
|
|
207
|
+
*
|
|
208
|
+
* @param {number} [channel=0] - Channel to check (default: 0)
|
|
209
|
+
* @returns {boolean|undefined} True if on, false if off, undefined if not available
|
|
210
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
211
|
+
*/
|
|
212
|
+
getDiffuserLightIsOn(channel = 0) {
|
|
213
|
+
this.validateState();
|
|
214
|
+
const lightState = this._diffuserLightStateByChannel.get(channel);
|
|
215
|
+
if (lightState) {
|
|
216
|
+
return lightState.isOn;
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Gets the cached diffuser spray state for the specified channel.
|
|
223
|
+
*
|
|
224
|
+
* Returns cached state without making a request. Use {@link getDiffuserSprayState} to fetch
|
|
225
|
+
* fresh state from the device. State is automatically updated when commands are sent or
|
|
226
|
+
* push notifications are received.
|
|
227
|
+
*
|
|
228
|
+
* @param {number} [channel=0] - Channel to get state for (default: 0)
|
|
229
|
+
* @returns {import('../lib/model/states/diffuser-spray-state').DiffuserSprayState|undefined} Cached spray state or undefined if not available
|
|
230
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
231
|
+
*/
|
|
232
|
+
getCachedDiffuserSprayState(channel = 0) {
|
|
233
|
+
this.validateState();
|
|
234
|
+
return this._diffuserSprayStateByChannel.get(channel);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Gets the diffuser spray mode for the specified channel (cached).
|
|
239
|
+
*
|
|
240
|
+
* Returns the spray mode enum from cached state. Use {@link getRawDiffuserSprayMode} to get
|
|
241
|
+
* the raw numeric value.
|
|
242
|
+
*
|
|
243
|
+
* @param {number} [channel=0] - Channel to get mode for (default: 0)
|
|
244
|
+
* @returns {import('../lib/enums').DiffuserSprayMode|undefined} DiffuserSprayMode enum object (e.g., DiffuserSprayMode.LIGHT) or undefined if not available
|
|
245
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
246
|
+
* @see getRawDiffuserSprayMode
|
|
247
|
+
*/
|
|
248
|
+
getDiffuserSprayMode(channel = 0) {
|
|
249
|
+
this.validateState();
|
|
250
|
+
const sprayState = this._diffuserSprayStateByChannel.get(channel);
|
|
251
|
+
if (sprayState && sprayState.mode !== undefined && sprayState.mode !== null) {
|
|
252
|
+
const enumKey = Object.keys(DiffuserSprayMode).find(key => DiffuserSprayMode[key] === sprayState.mode);
|
|
253
|
+
return enumKey ? DiffuserSprayMode[enumKey] : undefined;
|
|
254
|
+
}
|
|
255
|
+
return undefined;
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Gets the raw numeric diffuser spray mode value for the specified channel (cached).
|
|
260
|
+
*
|
|
261
|
+
* Returns the raw numeric mode value. For enum object, use {@link getDiffuserSprayMode} instead.
|
|
262
|
+
*
|
|
263
|
+
* @param {number} [channel=0] - Channel to get mode for (default: 0)
|
|
264
|
+
* @returns {number|undefined} Raw numeric mode value or undefined if not available
|
|
265
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
266
|
+
* @see getDiffuserSprayMode
|
|
267
|
+
*/
|
|
268
|
+
getRawDiffuserSprayMode(channel = 0) {
|
|
269
|
+
this.validateState();
|
|
270
|
+
const sprayState = this._diffuserSprayStateByChannel.get(channel);
|
|
271
|
+
if (sprayState) {
|
|
272
|
+
return sprayState.mode;
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Updates the cached diffuser light state from light data.
|
|
279
|
+
*
|
|
280
|
+
* Called automatically when diffuser light push notifications are received or commands complete.
|
|
281
|
+
* Handles both single objects and arrays of light data.
|
|
282
|
+
*
|
|
283
|
+
* @param {Object|Array} lightData - Light data (single object or array)
|
|
284
|
+
* @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
_updateDiffuserLightState(lightData, source = 'response') {
|
|
288
|
+
if (!lightData) {return;}
|
|
289
|
+
|
|
290
|
+
const lightArray = Array.isArray(lightData) ? lightData : [lightData];
|
|
291
|
+
|
|
292
|
+
for (const lightItem of lightArray) {
|
|
293
|
+
const channelIndex = lightItem.channel;
|
|
294
|
+
if (channelIndex === undefined || channelIndex === null) {continue;}
|
|
295
|
+
|
|
296
|
+
const oldState = this._diffuserLightStateByChannel.get(channelIndex);
|
|
297
|
+
const oldValue = oldState ? {
|
|
298
|
+
isOn: oldState.isOn,
|
|
299
|
+
brightness: oldState.luminance,
|
|
300
|
+
rgb: oldState.rgbTuple,
|
|
301
|
+
mode: oldState.mode
|
|
302
|
+
} : undefined;
|
|
303
|
+
|
|
304
|
+
let state = this._diffuserLightStateByChannel.get(channelIndex);
|
|
305
|
+
if (!state) {
|
|
306
|
+
state = new DiffuserLightState(lightItem);
|
|
307
|
+
this._diffuserLightStateByChannel.set(channelIndex, state);
|
|
308
|
+
} else {
|
|
309
|
+
state.update(lightItem);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const newValue = buildStateChanges(oldValue, {
|
|
313
|
+
isOn: state.isOn,
|
|
314
|
+
brightness: state.luminance,
|
|
315
|
+
rgb: state.rgbTuple,
|
|
316
|
+
mode: state.mode
|
|
317
|
+
}, ['rgb']);
|
|
318
|
+
|
|
319
|
+
if (Object.keys(newValue).length > 0) {
|
|
320
|
+
this.emit('stateChange', {
|
|
321
|
+
type: 'diffuserLight',
|
|
322
|
+
channel: channelIndex,
|
|
323
|
+
value: newValue,
|
|
324
|
+
oldValue,
|
|
325
|
+
source,
|
|
326
|
+
timestamp: Date.now()
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Updates the cached diffuser spray state from spray data.
|
|
334
|
+
*
|
|
335
|
+
* Called automatically when diffuser spray push notifications are received or commands complete.
|
|
336
|
+
* Handles both single objects and arrays of spray data.
|
|
337
|
+
*
|
|
338
|
+
* @param {Object|Array} sprayData - Spray data (single object or array)
|
|
339
|
+
* @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
|
|
340
|
+
* @private
|
|
341
|
+
*/
|
|
342
|
+
_updateDiffuserSprayState(sprayData, source = 'response') {
|
|
343
|
+
if (!sprayData) {return;}
|
|
344
|
+
|
|
345
|
+
const sprayArray = Array.isArray(sprayData) ? sprayData : [sprayData];
|
|
346
|
+
|
|
347
|
+
for (const sprayItem of sprayArray) {
|
|
348
|
+
const channelIndex = sprayItem.channel;
|
|
349
|
+
if (channelIndex === undefined || channelIndex === null) {continue;}
|
|
350
|
+
|
|
351
|
+
const oldState = this._diffuserSprayStateByChannel.get(channelIndex);
|
|
352
|
+
const oldValue = oldState ? {
|
|
353
|
+
mode: oldState.mode
|
|
354
|
+
} : undefined;
|
|
355
|
+
|
|
356
|
+
let state = this._diffuserSprayStateByChannel.get(channelIndex);
|
|
357
|
+
if (!state) {
|
|
358
|
+
state = new DiffuserSprayState(sprayItem);
|
|
359
|
+
this._diffuserSprayStateByChannel.set(channelIndex, state);
|
|
360
|
+
} else {
|
|
361
|
+
state.update(sprayItem);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const newValue = buildStateChanges(oldValue, {
|
|
365
|
+
mode: state.mode
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (Object.keys(newValue).length > 0) {
|
|
369
|
+
this.emit('stateChange', {
|
|
370
|
+
type: 'diffuserSpray',
|
|
371
|
+
channel: channelIndex,
|
|
372
|
+
value: newValue,
|
|
373
|
+
oldValue,
|
|
374
|
+
source,
|
|
375
|
+
timestamp: Date.now()
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Gets the diffuser sensor data (humidity and temperature) from the device.
|
|
383
|
+
*
|
|
384
|
+
* @returns {Promise<Object>} Response containing sensor data with humidity and temperature
|
|
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 getDiffuserSensor(_options = {}) {
|
|
389
|
+
return await this.publishMessage('GET', 'Appliance.Control.Diffuser.Sensor', {});
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Controls the diffuser sensor configuration.
|
|
394
|
+
*
|
|
395
|
+
* Note: This namespace primarily supports GET and PUSH. SET may not be available for all devices.
|
|
396
|
+
*
|
|
397
|
+
* @param {Object} sensorData - Sensor data object
|
|
398
|
+
* @returns {Promise<Object>} Response from the device
|
|
399
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
400
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
401
|
+
*/
|
|
402
|
+
async setDiffuserSensor(options = {}) {
|
|
403
|
+
if (!options.sensorData) {
|
|
404
|
+
throw new Error('sensorData is required');
|
|
405
|
+
}
|
|
406
|
+
const sensorData = options.sensorData;
|
|
407
|
+
const payload = sensorData;
|
|
408
|
+
return await this.publishMessage('SET', 'Appliance.Control.Diffuser.Sensor', payload);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Timer digest feature module.
|
|
5
|
+
* Provides access to timer summary information without fetching individual timer details.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
/**
|
|
9
|
+
* Gets timer digest (summary) information.
|
|
10
|
+
*
|
|
11
|
+
* Returns a summary of all timers across all channels. Use this to check timer status
|
|
12
|
+
* without fetching detailed timer information. For detailed timer data, use {@link getTimerX}.
|
|
13
|
+
*
|
|
14
|
+
* @returns {Promise<Object>} Response containing timer digest data
|
|
15
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
16
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
17
|
+
*/
|
|
18
|
+
async getTimerXDigest() {
|
|
19
|
+
return await this.publishMessage('GET', 'Appliance.Digest.TimerX', {});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Trigger digest feature module.
|
|
5
|
+
* Provides access to trigger summary information without fetching individual trigger details.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
/**
|
|
9
|
+
* Gets trigger digest (summary) information.
|
|
10
|
+
*
|
|
11
|
+
* Returns a summary of all triggers across all channels. Use this to check trigger status
|
|
12
|
+
* without fetching detailed trigger information. For detailed trigger data, use {@link getTriggerX}.
|
|
13
|
+
*
|
|
14
|
+
* @returns {Promise<Object>} Response containing trigger digest data
|
|
15
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
16
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
17
|
+
*/
|
|
18
|
+
async getTriggerXDigest() {
|
|
19
|
+
return await this.publishMessage('GET', 'Appliance.Digest.TriggerX', {});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { DNDMode } = require('../../model/enums');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Do-not-disturb feature module.
|
|
7
|
+
* Controls the device's do-not-disturb mode, which disables the ambient LED when enabled.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = {
|
|
10
|
+
/**
|
|
11
|
+
* Gets the do-not-disturb mode from the device.
|
|
12
|
+
* @param {Object} [options={}] - Get options
|
|
13
|
+
* @returns {Promise<import('../lib/enums').DNDMode>} DNDMode enum object (DNDMode.DND_DISABLED or DNDMode.DND_ENABLED)
|
|
14
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
15
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
16
|
+
*/
|
|
17
|
+
async getDNDMode(_options = {}) {
|
|
18
|
+
const result = await this.publishMessage('GET', 'Appliance.System.DNDMode', {});
|
|
19
|
+
if (result && result.DNDMode && result.DNDMode.mode !== undefined) {
|
|
20
|
+
const modeValue = result.DNDMode.mode;
|
|
21
|
+
const enumKey = Object.keys(DNDMode).find(key => DNDMode[key] === modeValue);
|
|
22
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
23
|
+
return enumKey ? DNDMode[enumKey] : DNDMode.DND_DISABLED;
|
|
24
|
+
}
|
|
25
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
26
|
+
return DNDMode.DND_DISABLED;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gets the raw numeric DND mode value from the device.
|
|
31
|
+
*
|
|
32
|
+
* Returns the numeric value directly without converting to an enum. For enum object,
|
|
33
|
+
* use {@link getDNDMode} instead.
|
|
34
|
+
* @param {Object} [options={}] - Get options
|
|
35
|
+
* @returns {Promise<number>} Raw numeric DND mode value (0 = disabled, 1 = enabled)
|
|
36
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
37
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
38
|
+
* @see getDNDMode
|
|
39
|
+
*/
|
|
40
|
+
async getRawDNDMode(_options = {}) {
|
|
41
|
+
const result = await this.publishMessage('GET', 'Appliance.System.DNDMode', {});
|
|
42
|
+
if (result && result.DNDMode && result.DNDMode.mode !== undefined) {
|
|
43
|
+
return result.DNDMode.mode;
|
|
44
|
+
}
|
|
45
|
+
return DNDMode.DND_DISABLED;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Controls the do-not-disturb mode setting.
|
|
50
|
+
*
|
|
51
|
+
* When enabled, the device turns off its ambient LED to reduce visual disturbance.
|
|
52
|
+
*
|
|
53
|
+
* @param {Object} options - DND mode options
|
|
54
|
+
* @param {boolean|import('../lib/enums').DNDMode} options.mode - DNDMode enum value or boolean (true = enabled, false = disabled)
|
|
55
|
+
* @returns {Promise<void>}
|
|
56
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
57
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
58
|
+
* @throws {import('../lib/errors/errors').CommandError} If mode value is invalid
|
|
59
|
+
*/
|
|
60
|
+
async setDNDMode(options = {}) {
|
|
61
|
+
if (options.mode === undefined) {
|
|
62
|
+
const { CommandError } = require('../../model/exception');
|
|
63
|
+
throw new CommandError('mode is required', { options }, this.uuid);
|
|
64
|
+
}
|
|
65
|
+
let modeValue;
|
|
66
|
+
if (typeof options.mode === 'boolean') {
|
|
67
|
+
modeValue = options.mode ? DNDMode.DND_ENABLED : DNDMode.DND_DISABLED;
|
|
68
|
+
} else if (options.mode === DNDMode.DND_ENABLED || options.mode === DNDMode.DND_DISABLED) {
|
|
69
|
+
modeValue = options.mode;
|
|
70
|
+
} else {
|
|
71
|
+
const { CommandError } = require('../../model/exception');
|
|
72
|
+
throw new CommandError('Invalid DND mode. Expected boolean or DNDMode enum value.', { mode: options.mode }, this.uuid);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const payload = { 'DNDMode': { 'mode': modeValue } };
|
|
76
|
+
await this.publishMessage('SET', 'Appliance.System.DNDMode', payload);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { normalizeChannel } = require('../../utilities/options');
|
|
4
|
+
const { buildStateChanges } = require('../../utilities/state-changes');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Electricity feature module.
|
|
8
|
+
* Provides access to real-time power consumption metrics including voltage, current, and power.
|
|
9
|
+
*/
|
|
10
|
+
module.exports = {
|
|
11
|
+
/**
|
|
12
|
+
* Initializes electricity metrics cache.
|
|
13
|
+
*
|
|
14
|
+
* Called lazily when electricity data is first accessed to avoid unnecessary initialization.
|
|
15
|
+
*
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
_initializeElectricityCache() {
|
|
19
|
+
if (this._channelCachedSamples === undefined) {
|
|
20
|
+
this._channelCachedSamples = new Map();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Gets instant power consumption metrics from the device.
|
|
26
|
+
*
|
|
27
|
+
* Note that voltage and current values reported by most Meross devices may not be accurate.
|
|
28
|
+
* The power (wattage) value is typically more reliable. Use the power attribute directly
|
|
29
|
+
* rather than calculating it as voltage * current.
|
|
30
|
+
*
|
|
31
|
+
* To avoid excessive device polling, prefer using {@link getCachedElectricity} and only
|
|
32
|
+
* call this method when the cached value is null or the sampleTimestamp indicates stale data.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} [options={}] - Get options
|
|
35
|
+
* @param {number} [options.channel=0] - Channel to read metrics from (default: 0)
|
|
36
|
+
* @returns {Promise<{amperage: number, voltage: number, wattage: number, sampleTimestamp: Date}>} PowerInfo object with converted units
|
|
37
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
38
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
39
|
+
*/
|
|
40
|
+
async getElectricity(options = {}) {
|
|
41
|
+
const channel = normalizeChannel(options);
|
|
42
|
+
this._initializeElectricityCache();
|
|
43
|
+
|
|
44
|
+
const result = await this.publishMessage('GET', 'Appliance.Control.Electricity', { channel });
|
|
45
|
+
const data = result && result.electricity ? result.electricity : {};
|
|
46
|
+
|
|
47
|
+
this._updateElectricityState({ channel, ...data }, 'response');
|
|
48
|
+
|
|
49
|
+
return this._channelCachedSamples.get(channel) || null;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Gets the cached power consumption metrics.
|
|
54
|
+
*
|
|
55
|
+
* Returns the most recently fetched power info without making a request. Use {@link getElectricity}
|
|
56
|
+
* to fetch fresh data from the device.
|
|
57
|
+
*
|
|
58
|
+
* @param {number} [channel=0] - Channel to get metrics for (default: 0)
|
|
59
|
+
* @returns {{amperage: number, voltage: number, wattage: number, sampleTimestamp: Date}|null} Cached PowerInfo object or null if not available
|
|
60
|
+
*/
|
|
61
|
+
getCachedElectricity(channel = 0) {
|
|
62
|
+
this._initializeElectricityCache();
|
|
63
|
+
return this._channelCachedSamples.get(channel) || null;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Gets the raw electricity metrics response without parsing or unit conversion.
|
|
68
|
+
*
|
|
69
|
+
* Returns the raw API response with device-native units. For parsed data with standard
|
|
70
|
+
* unit conversions, use {@link getElectricity} instead.
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} [options={}] - Get options
|
|
73
|
+
* @param {number} [options.channel=0] - Channel to read metrics from (default: 0)
|
|
74
|
+
* @returns {Promise<Object>} Raw API response containing electricity data
|
|
75
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
76
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
77
|
+
*/
|
|
78
|
+
async getRawElectricity(options = {}) {
|
|
79
|
+
const channel = normalizeChannel(options);
|
|
80
|
+
return await this.publishMessage('GET', 'Appliance.Control.Electricity', { channel });
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Updates the cached electricity state from electricity data.
|
|
85
|
+
*
|
|
86
|
+
* Called automatically when electricity data is received. Parses raw device data,
|
|
87
|
+
* converts units, and emits stateChange events when values change.
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} electricityData - Raw electricity data from device
|
|
90
|
+
* @param {number} electricityData.channel - Channel index
|
|
91
|
+
* @param {number} [electricityData.current] - Current in device units (milliamps)
|
|
92
|
+
* @param {number} [electricityData.voltage] - Voltage in device units (tenths of volts)
|
|
93
|
+
* @param {number} [electricityData.power] - Power in device units (milliwatts)
|
|
94
|
+
* @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
_updateElectricityState(electricityData, source = 'response') {
|
|
98
|
+
if (!electricityData) {return;}
|
|
99
|
+
|
|
100
|
+
this._initializeElectricityCache();
|
|
101
|
+
|
|
102
|
+
const channelIndex = electricityData.channel;
|
|
103
|
+
if (channelIndex === undefined || channelIndex === null) {return;}
|
|
104
|
+
|
|
105
|
+
const current = parseFloat(electricityData.current || 0) / 1000;
|
|
106
|
+
const voltage = parseFloat(electricityData.voltage || 0) / 10;
|
|
107
|
+
const power = parseFloat(electricityData.power || 0) / 1000;
|
|
108
|
+
|
|
109
|
+
const powerInfo = {
|
|
110
|
+
amperage: current,
|
|
111
|
+
voltage,
|
|
112
|
+
wattage: power,
|
|
113
|
+
sampleTimestamp: new Date()
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const oldPowerInfo = this._channelCachedSamples.get(channelIndex);
|
|
117
|
+
const oldValue = oldPowerInfo ? {
|
|
118
|
+
amperage: oldPowerInfo.amperage,
|
|
119
|
+
voltage: oldPowerInfo.voltage,
|
|
120
|
+
wattage: oldPowerInfo.wattage
|
|
121
|
+
} : undefined;
|
|
122
|
+
|
|
123
|
+
this._channelCachedSamples.set(channelIndex, powerInfo);
|
|
124
|
+
|
|
125
|
+
const newValue = buildStateChanges(oldValue, {
|
|
126
|
+
amperage: powerInfo.amperage,
|
|
127
|
+
voltage: powerInfo.voltage,
|
|
128
|
+
wattage: powerInfo.wattage
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (Object.keys(newValue).length > 0) {
|
|
132
|
+
const valueToEmit = oldValue === undefined ? powerInfo : { ...newValue, sampleTimestamp: powerInfo.sampleTimestamp };
|
|
133
|
+
|
|
134
|
+
this.emit('stateChange', {
|
|
135
|
+
type: 'electricity',
|
|
136
|
+
channel: channelIndex,
|
|
137
|
+
value: valueToEmit,
|
|
138
|
+
oldValue,
|
|
139
|
+
source,
|
|
140
|
+
timestamp: Date.now()
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|