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,408 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const LightState = require('../../model/states/light-state');
|
|
4
|
+
const { LightMode } = require('../../model/enums');
|
|
5
|
+
const { rgbToInt } = require('../../utilities/conversion');
|
|
6
|
+
const { normalizeChannel } = require('../../utilities/options');
|
|
7
|
+
const { buildStateChanges } = require('../../utilities/state-changes');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Light feature module.
|
|
11
|
+
* Provides control over light settings including color, brightness, temperature, and on/off state.
|
|
12
|
+
*/
|
|
13
|
+
module.exports = {
|
|
14
|
+
/**
|
|
15
|
+
* Controls the light settings.
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} options - Light options
|
|
18
|
+
* @param {Object} options.light - Light configuration object
|
|
19
|
+
* @returns {Promise<Object>} Response from the device
|
|
20
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
21
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
22
|
+
*/
|
|
23
|
+
async setLight(options = {}) {
|
|
24
|
+
if (!options.light) {
|
|
25
|
+
throw new Error('light configuration object is required');
|
|
26
|
+
}
|
|
27
|
+
const payload = { light: options.light };
|
|
28
|
+
const response = await this.publishMessage('SET', 'Appliance.Control.Light', payload, null);
|
|
29
|
+
|
|
30
|
+
if (response && response.light) {
|
|
31
|
+
this._updateLightState(response.light, 'response');
|
|
32
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
33
|
+
} else if (options.light) {
|
|
34
|
+
this._updateLightState(options.light, 'response');
|
|
35
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return response;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets the current light state from the device.
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} [options={}] - Get options
|
|
45
|
+
* @returns {Promise<Object>} Response containing light state
|
|
46
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
47
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
48
|
+
*/
|
|
49
|
+
async getLightState(_options = {}) {
|
|
50
|
+
const response = await this.publishMessage('GET', 'Appliance.Control.Light', {}, null);
|
|
51
|
+
if (response && response.light) {
|
|
52
|
+
this._updateLightState(response.light, 'response');
|
|
53
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
54
|
+
}
|
|
55
|
+
return response;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Gets the cached light state for the specified channel.
|
|
60
|
+
*
|
|
61
|
+
* @param {number} [channel=0] - Channel to get state for (default: 0)
|
|
62
|
+
* @returns {Object|undefined} Cached light state or undefined if not available
|
|
63
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
64
|
+
*/
|
|
65
|
+
getCachedLightState(channel = 0) {
|
|
66
|
+
this.validateState();
|
|
67
|
+
return this._lightStateByChannel.get(channel);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Checks if the light is on for the specified channel.
|
|
72
|
+
*
|
|
73
|
+
* For devices that support ToggleX and Toggle abilities, the onoff state is not exposed
|
|
74
|
+
* in the light status. In that case, we return the toggle state.
|
|
75
|
+
*
|
|
76
|
+
* @param {number} [channel=0] - Channel to check (default: 0)
|
|
77
|
+
* @returns {boolean|undefined} True if on, false if off, undefined if not available
|
|
78
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
79
|
+
*/
|
|
80
|
+
getLightIsOn(channel = 0) {
|
|
81
|
+
this.validateState();
|
|
82
|
+
if (this._abilities) {
|
|
83
|
+
const hasToggleX = this._abilities['Appliance.Control.ToggleX'];
|
|
84
|
+
const hasToggle = this._abilities['Appliance.Control.Toggle'];
|
|
85
|
+
if (hasToggleX || hasToggle) {
|
|
86
|
+
if (typeof this.isOn === 'function') {
|
|
87
|
+
return this.isOn(channel);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const lightState = this._lightStateByChannel.get(channel);
|
|
92
|
+
if (lightState) {
|
|
93
|
+
return lightState.isOn;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Gets the light RGB color for the specified channel.
|
|
100
|
+
*
|
|
101
|
+
* @param {number} [channel=0] - Channel to get color for (default: 0)
|
|
102
|
+
* @returns {Array<number>|undefined} RGB tuple [r, g, b] or undefined if not available
|
|
103
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
104
|
+
*/
|
|
105
|
+
getLightRgbColor(channel = 0) {
|
|
106
|
+
this.validateState();
|
|
107
|
+
const lightState = this._lightStateByChannel.get(channel);
|
|
108
|
+
if (lightState) {
|
|
109
|
+
return lightState.rgbTuple;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Gets the light brightness for the specified channel.
|
|
116
|
+
*
|
|
117
|
+
* @param {number} [channel=0] - Channel to get brightness for (default: 0)
|
|
118
|
+
* @returns {number|undefined} Brightness value or undefined if not available
|
|
119
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
120
|
+
*/
|
|
121
|
+
getLightBrightness(channel = 0) {
|
|
122
|
+
this.validateState();
|
|
123
|
+
const lightState = this._lightStateByChannel.get(channel);
|
|
124
|
+
if (lightState) {
|
|
125
|
+
return lightState.luminance;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Gets the light temperature for the specified channel.
|
|
132
|
+
*
|
|
133
|
+
* @param {number} [channel=0] - Channel to get temperature for (default: 0)
|
|
134
|
+
* @returns {number|undefined} Temperature value or undefined if not available
|
|
135
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
136
|
+
*/
|
|
137
|
+
getLightTemperature(channel = 0) {
|
|
138
|
+
this.validateState();
|
|
139
|
+
const lightState = this._lightStateByChannel.get(channel);
|
|
140
|
+
if (lightState) {
|
|
141
|
+
return lightState.temperature;
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Gets the light mode (capacity) for the specified channel.
|
|
148
|
+
*
|
|
149
|
+
* @param {number} [channel=0] - Channel to get mode for (default: 0)
|
|
150
|
+
* @returns {number|undefined} Light mode/capacity or undefined if not available
|
|
151
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
152
|
+
*/
|
|
153
|
+
getLightMode(channel = 0) {
|
|
154
|
+
this.validateState();
|
|
155
|
+
const lightState = this._lightStateByChannel.get(channel);
|
|
156
|
+
if (lightState) {
|
|
157
|
+
return lightState.capacity;
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Checks if the light supports RGB mode for the specified channel.
|
|
164
|
+
*
|
|
165
|
+
* @param {number} [channel=0] - Channel to check (default: 0)
|
|
166
|
+
* @returns {boolean} True if RGB is supported
|
|
167
|
+
*/
|
|
168
|
+
getSupportsRgb(channel = 0) {
|
|
169
|
+
return this._supportsLightMode(LightMode.MODE_RGB, channel);
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Checks if the light supports luminance mode for the specified channel.
|
|
174
|
+
*
|
|
175
|
+
* @param {number} [channel=0] - Channel to check (default: 0)
|
|
176
|
+
* @returns {boolean} True if luminance is supported
|
|
177
|
+
*/
|
|
178
|
+
getSupportsLuminance(channel = 0) {
|
|
179
|
+
return this._supportsLightMode(LightMode.MODE_LUMINANCE, channel);
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Checks if the light supports temperature mode for the specified channel.
|
|
184
|
+
*
|
|
185
|
+
* @param {number} [channel=0] - Channel to check (default: 0)
|
|
186
|
+
* @returns {boolean} True if temperature is supported
|
|
187
|
+
*/
|
|
188
|
+
getSupportsTemperature(channel = 0) {
|
|
189
|
+
return this._supportsLightMode(LightMode.MODE_TEMPERATURE, channel);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Turns on the light for the specified channel.
|
|
194
|
+
*
|
|
195
|
+
* Automatically selects the appropriate control method (ToggleX/Toggle or Light) based on
|
|
196
|
+
* device capabilities.
|
|
197
|
+
*
|
|
198
|
+
* @param {Object} [options={}] - Turn on options
|
|
199
|
+
* @param {number} [options.channel=0] - Channel to control (default: 0)
|
|
200
|
+
* @returns {Promise<Object>} Response from the device
|
|
201
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
202
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
203
|
+
*/
|
|
204
|
+
async turnOn(options = {}) {
|
|
205
|
+
if (this._abilities) {
|
|
206
|
+
const hasToggleX = this._abilities['Appliance.Control.ToggleX'];
|
|
207
|
+
const hasToggle = this._abilities['Appliance.Control.Toggle'];
|
|
208
|
+
|
|
209
|
+
if (hasToggleX && typeof this.setToggleX === 'function') {
|
|
210
|
+
return await this.setToggleX({ ...options, onoff: true });
|
|
211
|
+
} else if (hasToggle && typeof this.setToggle === 'function') {
|
|
212
|
+
return await this.setToggle({ onoff: true });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return await this.setLightColor({ ...options, onoff: true });
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Turns off the light for the specified channel.
|
|
221
|
+
*
|
|
222
|
+
* Automatically selects the appropriate control method (ToggleX/Toggle or Light) based on
|
|
223
|
+
* device capabilities.
|
|
224
|
+
*
|
|
225
|
+
* @param {Object} [options={}] - Turn off options
|
|
226
|
+
* @param {number} [options.channel=0] - Channel to control (default: 0)
|
|
227
|
+
* @returns {Promise<Object>} Response from the device
|
|
228
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
229
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
230
|
+
*/
|
|
231
|
+
async turnOff(options = {}) {
|
|
232
|
+
if (this._abilities) {
|
|
233
|
+
const hasToggleX = this._abilities['Appliance.Control.ToggleX'];
|
|
234
|
+
const hasToggle = this._abilities['Appliance.Control.Toggle'];
|
|
235
|
+
|
|
236
|
+
if (hasToggleX && typeof this.setToggleX === 'function') {
|
|
237
|
+
return await this.setToggleX({ ...options, onoff: false });
|
|
238
|
+
} else if (hasToggle && typeof this.setToggle === 'function') {
|
|
239
|
+
return await this.setToggle({ onoff: false });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return await this.setLightColor({ ...options, onoff: false });
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Controls the light color, brightness, and temperature.
|
|
248
|
+
*
|
|
249
|
+
* Allows setting multiple light properties in a single call. If ToggleX or Toggle is
|
|
250
|
+
* supported, uses those for on/off control; otherwise includes onoff in the light payload.
|
|
251
|
+
*
|
|
252
|
+
* @param {Object} [options={}] - Light control options
|
|
253
|
+
* @param {number} [options.channel] - Channel to control (default: 0)
|
|
254
|
+
* @param {boolean} [options.onoff] - Turn on/off
|
|
255
|
+
* @param {Array<number>|number} [options.rgb] - RGB color [r, g, b] or integer
|
|
256
|
+
* @param {number} [options.luminance] - Brightness value
|
|
257
|
+
* @param {number} [options.temperature] - Temperature value
|
|
258
|
+
* @param {boolean|number} [options.gradual] - Enable gradual transition (default: true for RGB, false otherwise)
|
|
259
|
+
* @returns {Promise<Object|null>} Response from the device or null if no changes needed
|
|
260
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
261
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
262
|
+
*/
|
|
263
|
+
async setLightColor(options = {}) {
|
|
264
|
+
const channel = normalizeChannel(options);
|
|
265
|
+
const { onoff, rgb, luminance, temperature, gradual } = options;
|
|
266
|
+
|
|
267
|
+
const currentIsOn = this.getLightIsOn(channel);
|
|
268
|
+
const hasToggleX = this._abilities && this._abilities['Appliance.Control.ToggleX'];
|
|
269
|
+
const hasToggle = this._abilities && this._abilities['Appliance.Control.Toggle'];
|
|
270
|
+
const hasToggleSupport = hasToggleX || hasToggle;
|
|
271
|
+
|
|
272
|
+
if (hasToggleSupport && onoff === false && currentIsOn !== false) {
|
|
273
|
+
if (hasToggleX && typeof this.setToggleX === 'function') {
|
|
274
|
+
await this.setToggleX({ channel, onoff: false });
|
|
275
|
+
} else if (hasToggle && typeof this.setToggle === 'function') {
|
|
276
|
+
await this.setToggle({ onoff: false });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const lightPayload = {};
|
|
281
|
+
|
|
282
|
+
if (!hasToggleSupport) {
|
|
283
|
+
if (onoff !== undefined) {
|
|
284
|
+
lightPayload.onoff = onoff ? 1 : 0;
|
|
285
|
+
} else if (currentIsOn !== undefined) {
|
|
286
|
+
lightPayload.onoff = currentIsOn ? 1 : 0;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (rgb !== undefined && this._supportsLightMode(LightMode.MODE_RGB, channel)) {
|
|
291
|
+
lightPayload.rgb = rgbToInt(rgb);
|
|
292
|
+
lightPayload.capacity = (lightPayload.capacity || 0) | LightMode.MODE_RGB;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (luminance !== undefined && this._supportsLightMode(LightMode.MODE_LUMINANCE, channel)) {
|
|
296
|
+
lightPayload.luminance = luminance;
|
|
297
|
+
lightPayload.capacity = (lightPayload.capacity || 0) | LightMode.MODE_LUMINANCE;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (temperature !== undefined && this._supportsLightMode(LightMode.MODE_TEMPERATURE, channel)) {
|
|
301
|
+
lightPayload.temperature = temperature;
|
|
302
|
+
lightPayload.capacity = (lightPayload.capacity || 0) | LightMode.MODE_TEMPERATURE;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (Object.keys(lightPayload).length === 0) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
lightPayload.channel = channel;
|
|
310
|
+
|
|
311
|
+
// Handle gradual transition parameter
|
|
312
|
+
if (gradual !== undefined) {
|
|
313
|
+
// Allow both boolean and number (0/1) for convenience
|
|
314
|
+
lightPayload.gradual = typeof gradual === 'boolean' ? (gradual ? 1 : 0) : gradual;
|
|
315
|
+
} else if (rgb !== undefined) {
|
|
316
|
+
// Default to gradual for RGB changes (matches app behavior)
|
|
317
|
+
lightPayload.gradual = 1;
|
|
318
|
+
} else {
|
|
319
|
+
// Default to instant for non-RGB changes
|
|
320
|
+
lightPayload.gradual = 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const payload = { light: lightPayload };
|
|
324
|
+
const response = await this.publishMessage('SET', 'Appliance.Control.Light', payload, null);
|
|
325
|
+
|
|
326
|
+
if (response && response.light) {
|
|
327
|
+
this._updateLightState(response.light, 'response');
|
|
328
|
+
} else {
|
|
329
|
+
this._updateLightState(lightPayload, 'response');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return response;
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Checks if the device supports a specific light mode for the given channel.
|
|
337
|
+
*
|
|
338
|
+
* @param {number} mode - Light mode to check (from LightMode enum)
|
|
339
|
+
* @param {number} [channel=0] - Channel to check (default: 0)
|
|
340
|
+
* @returns {boolean} True if the mode is supported
|
|
341
|
+
* @private
|
|
342
|
+
*/
|
|
343
|
+
_supportsLightMode(mode) {
|
|
344
|
+
if (!this._abilities) {return false;}
|
|
345
|
+
|
|
346
|
+
const lightAbility = this._abilities['Appliance.Control.Light'];
|
|
347
|
+
if (!lightAbility || !lightAbility.capacity) {return false;}
|
|
348
|
+
|
|
349
|
+
const { capacity } = lightAbility;
|
|
350
|
+
return (capacity & mode) === mode;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Updates the cached light state from light data.
|
|
355
|
+
*
|
|
356
|
+
* Called automatically when light push notifications are received or commands complete.
|
|
357
|
+
* Handles both single objects and arrays of light data.
|
|
358
|
+
*
|
|
359
|
+
* @param {Object|Array} lightData - Light data (single object or array)
|
|
360
|
+
* @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
363
|
+
_updateLightState(lightData, source = 'response') {
|
|
364
|
+
if (!lightData) {return;}
|
|
365
|
+
|
|
366
|
+
const lightArray = Array.isArray(lightData) ? lightData : [lightData];
|
|
367
|
+
|
|
368
|
+
for (const lightItem of lightArray) {
|
|
369
|
+
const channelIndex = lightItem.channel;
|
|
370
|
+
if (channelIndex === undefined || channelIndex === null) {continue;}
|
|
371
|
+
|
|
372
|
+
const oldState = this._lightStateByChannel.get(channelIndex);
|
|
373
|
+
const oldValue = oldState ? {
|
|
374
|
+
isOn: oldState.isOn,
|
|
375
|
+
brightness: oldState.luminance,
|
|
376
|
+
rgb: oldState.rgbTuple,
|
|
377
|
+
temperature: oldState.temperature
|
|
378
|
+
} : undefined;
|
|
379
|
+
|
|
380
|
+
let state = this._lightStateByChannel.get(channelIndex);
|
|
381
|
+
if (!state) {
|
|
382
|
+
state = new LightState(lightItem);
|
|
383
|
+
this._lightStateByChannel.set(channelIndex, state);
|
|
384
|
+
} else {
|
|
385
|
+
state.update(lightItem);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const newValue = buildStateChanges(oldValue, {
|
|
389
|
+
isOn: state.isOn,
|
|
390
|
+
brightness: state.luminance,
|
|
391
|
+
rgb: state.rgbTuple,
|
|
392
|
+
temperature: state.temperature
|
|
393
|
+
}, ['rgb']);
|
|
394
|
+
|
|
395
|
+
if (Object.keys(newValue).length > 0) {
|
|
396
|
+
this.emit('stateChange', {
|
|
397
|
+
type: 'light',
|
|
398
|
+
channel: channelIndex,
|
|
399
|
+
value: newValue,
|
|
400
|
+
oldValue,
|
|
401
|
+
source,
|
|
402
|
+
timestamp: Date.now()
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|