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,507 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TimerState = require('../../model/states/timer-state');
|
|
4
|
+
const { normalizeChannel } = require('../../utilities/options');
|
|
5
|
+
const { createTimer } = require('../../utilities/timer');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Timer feature module.
|
|
9
|
+
* Provides control over device timers that can trigger on/off actions at specified times.
|
|
10
|
+
*/
|
|
11
|
+
module.exports = {
|
|
12
|
+
/**
|
|
13
|
+
* Gets timer information for a specific timer ID or all timers for a channel.
|
|
14
|
+
*
|
|
15
|
+
* Use {@link getCachedTimerX} to get cached timers without making a request.
|
|
16
|
+
*
|
|
17
|
+
* According to the Meross API specification, GET requests require a timer ID.
|
|
18
|
+
* When a channel number is provided, this method uses Appliance.Digest.TimerX
|
|
19
|
+
* to retrieve all timer IDs for that channel, then queries each timer individually.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} [options={}] - Get options
|
|
22
|
+
* @param {string} [options.timerId] - Timer ID (string). If provided, gets specific timer
|
|
23
|
+
* @param {number} [options.channel=0] - Channel number. If timerId not provided, gets all timers for this channel
|
|
24
|
+
* @returns {Promise<Object>} Response containing timer data with `timerx` array
|
|
25
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
26
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
27
|
+
*/
|
|
28
|
+
async getTimerX(options = {}) {
|
|
29
|
+
if (options.timerId && typeof options.timerId === 'string') {
|
|
30
|
+
return await this._getTimerXById(options.timerId);
|
|
31
|
+
}
|
|
32
|
+
return await this._getTimerXByChannel(normalizeChannel(options));
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gets a timer by its ID.
|
|
37
|
+
* Tries GET request first, then falls back to cached state.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} timerId - Timer ID to retrieve
|
|
40
|
+
* @returns {Promise<Object>} Response containing timer data
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
async _getTimerXById(timerId) {
|
|
44
|
+
try {
|
|
45
|
+
const payload = {
|
|
46
|
+
timerx: {
|
|
47
|
+
id: timerId
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const response = await this.publishMessage('GET', 'Appliance.Control.TimerX', payload);
|
|
51
|
+
if (response && response.timerx) {
|
|
52
|
+
this._updateTimerXState(response.timerx, 'response');
|
|
53
|
+
return response;
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cachedTimers = this.getCachedTimerX ? this.getCachedTimerX() : null;
|
|
59
|
+
if (cachedTimers && Array.isArray(cachedTimers)) {
|
|
60
|
+
const timer = cachedTimers.find(t => t.id === timerId);
|
|
61
|
+
if (timer) {
|
|
62
|
+
return {
|
|
63
|
+
timerx: timer.toObject ? timer.toObject() : timer,
|
|
64
|
+
digest: []
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this._buildEmptyTimerResponse();
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets all timers for a specific channel.
|
|
74
|
+
* Tries cached state first, then falls back to GET requests via digest.
|
|
75
|
+
*
|
|
76
|
+
* @param {number} channel - Channel number
|
|
77
|
+
* @returns {Promise<Object>} Response containing timer data
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
async _getTimerXByChannel(channel) {
|
|
81
|
+
const cachedResponse = this._getTimerXFromCache(channel);
|
|
82
|
+
if (cachedResponse) {
|
|
83
|
+
return cachedResponse;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return await this._queryTimerXByChannel(channel);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Gets timers from cache for a specific channel.
|
|
91
|
+
*
|
|
92
|
+
* @param {number} channel - Channel number
|
|
93
|
+
* @returns {Object|null} Response containing cached timer data, or null if not available
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
_getTimerXFromCache(channel) {
|
|
97
|
+
const cachedTimers = this.getCachedTimerX ? this.getCachedTimerX(channel) : null;
|
|
98
|
+
if (cachedTimers && Array.isArray(cachedTimers) && cachedTimers.length > 0) {
|
|
99
|
+
return {
|
|
100
|
+
timerx: cachedTimers.map(t => t.toObject ? t.toObject() : t),
|
|
101
|
+
digest: cachedTimers.map(t => ({
|
|
102
|
+
id: t.id,
|
|
103
|
+
channel: t.channel,
|
|
104
|
+
count: t.count
|
|
105
|
+
}))
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Queries timers for a channel from the device by fetching digest and querying each timer individually.
|
|
113
|
+
*
|
|
114
|
+
* @param {number} channel - Channel number
|
|
115
|
+
* @returns {Promise<Object>} Response containing timer data
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
async _queryTimerXByChannel(channel) {
|
|
119
|
+
try {
|
|
120
|
+
const digestResponse = await this.getTimerXDigest();
|
|
121
|
+
if (!digestResponse || !digestResponse.digest || !Array.isArray(digestResponse.digest)) {
|
|
122
|
+
return this._buildEmptyTimerResponse();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const channelTimerIds = this._extractTimerIdsForChannel(digestResponse.digest, channel);
|
|
126
|
+
if (channelTimerIds.length === 0) {
|
|
127
|
+
return this._buildEmptyTimerResponse();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const allTimers = await this._queryTimersByIds(channelTimerIds);
|
|
131
|
+
if (allTimers.length > 0) {
|
|
132
|
+
const combinedResponse = {
|
|
133
|
+
timerx: allTimers,
|
|
134
|
+
digest: digestResponse.digest.filter(d => d.channel === channel)
|
|
135
|
+
};
|
|
136
|
+
this._updateTimerXState(allTimers, 'response');
|
|
137
|
+
return combinedResponse;
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return this._buildEmptyTimerResponse();
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Extracts timer IDs for a specific channel from digest data.
|
|
147
|
+
*
|
|
148
|
+
* @param {Array} digest - Digest array from getTimerXDigest response
|
|
149
|
+
* @param {number} channel - Channel number
|
|
150
|
+
* @returns {Array<string>} Array of timer IDs for the channel
|
|
151
|
+
* @private
|
|
152
|
+
*/
|
|
153
|
+
_extractTimerIdsForChannel(digest, channel) {
|
|
154
|
+
return digest
|
|
155
|
+
.filter(d => d.channel === channel)
|
|
156
|
+
.map(d => d.id);
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Queries multiple timers by their IDs in parallel.
|
|
161
|
+
*
|
|
162
|
+
* @param {Array<string>} timerIds - Array of timer IDs to query
|
|
163
|
+
* @returns {Promise<Array>} Array of timer objects
|
|
164
|
+
* @private
|
|
165
|
+
*/
|
|
166
|
+
async _queryTimersByIds(timerIds) {
|
|
167
|
+
const timerPromises = timerIds.map(id => {
|
|
168
|
+
const payload = { timerx: { id } };
|
|
169
|
+
return this.publishMessage('GET', 'Appliance.Control.TimerX', payload).catch(() => null);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const timerResponses = await Promise.all(timerPromises);
|
|
173
|
+
return this._parseTimerResponses(timerResponses);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Parses timer responses and extracts timer data, handling both array and single object formats.
|
|
178
|
+
*
|
|
179
|
+
* @param {Array} timerResponses - Array of timer response objects
|
|
180
|
+
* @returns {Array} Flattened array of timer objects
|
|
181
|
+
* @private
|
|
182
|
+
*/
|
|
183
|
+
_parseTimerResponses(timerResponses) {
|
|
184
|
+
return timerResponses
|
|
185
|
+
.filter(r => r && r.timerx)
|
|
186
|
+
.map(r => {
|
|
187
|
+
const timerx = r.timerx;
|
|
188
|
+
return Array.isArray(timerx) ? timerx : [timerx];
|
|
189
|
+
})
|
|
190
|
+
.flat();
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Builds an empty timer response object.
|
|
195
|
+
*
|
|
196
|
+
* @returns {Object} Empty response with timerx and digest arrays
|
|
197
|
+
* @private
|
|
198
|
+
*/
|
|
199
|
+
_buildEmptyTimerResponse() {
|
|
200
|
+
return {
|
|
201
|
+
timerx: [],
|
|
202
|
+
digest: []
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Controls a timer (creates or updates).
|
|
208
|
+
*
|
|
209
|
+
* Accepts either a user-friendly format (which will be converted via createTimer) or
|
|
210
|
+
* a raw timerx object. If a timer ID is provided, updates the existing timer. Otherwise,
|
|
211
|
+
* creates a new timer with a generated ID.
|
|
212
|
+
*
|
|
213
|
+
* @param {Object} options - Timer options
|
|
214
|
+
* @param {Object} [options.timerx] - Raw timer configuration object (if provided, used directly)
|
|
215
|
+
* @param {string} [options.timerx.id] - Timer ID (required for updates, generated for new timers)
|
|
216
|
+
* @param {number} [options.timerx.channel] - Channel the timer belongs to
|
|
217
|
+
* @param {number} [options.timerx.onoff] - Turn device on (1) or off (0) when timer triggers
|
|
218
|
+
* @param {number} [options.timerx.type] - Timer type (use TimerType enum)
|
|
219
|
+
* @param {number} [options.timerx.time] - Time value (depends on timer type)
|
|
220
|
+
* @param {string|Date|number} [options.time] - Time in HH:MM format, Date object, or minutes (user-friendly format)
|
|
221
|
+
* @param {Array<string|number>|number} [options.days] - Days of week (array of names/numbers) or week bitmask (user-friendly format)
|
|
222
|
+
* @param {boolean} [options.on] - Whether to turn device on (true) or off (false) when timer triggers (user-friendly format)
|
|
223
|
+
* @param {string} [options.alias] - Timer name/alias (user-friendly format)
|
|
224
|
+
* @param {number} [options.channel] - Channel number (user-friendly format)
|
|
225
|
+
* @param {boolean} [options.enabled] - Whether timer is enabled (user-friendly format)
|
|
226
|
+
* @param {string} [options.id] - Timer ID (user-friendly format)
|
|
227
|
+
* @returns {Promise<Object>} Response from the device containing the updated timer
|
|
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 setTimerX(options = {}) {
|
|
232
|
+
let timerx;
|
|
233
|
+
|
|
234
|
+
if (options.timerx) {
|
|
235
|
+
timerx = options.timerx;
|
|
236
|
+
} else {
|
|
237
|
+
timerx = createTimer(options);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const payload = { timerx };
|
|
241
|
+
const response = await this.publishMessage('SET', 'Appliance.Control.TimerX', payload);
|
|
242
|
+
if (response && response.timerx) {
|
|
243
|
+
this._updateTimerXState(response.timerx);
|
|
244
|
+
} else if (timerx) {
|
|
245
|
+
this._updateTimerXState([timerx]);
|
|
246
|
+
}
|
|
247
|
+
return response;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Deletes a timer by ID.
|
|
252
|
+
*
|
|
253
|
+
* @param {Object} options - Delete options
|
|
254
|
+
* @param {string} options.timerId - Timer ID to delete
|
|
255
|
+
* @param {number} [options.channel=0] - Channel the timer belongs to (default: 0)
|
|
256
|
+
* @returns {Promise<Object>} Response from the device
|
|
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 deleteTimerX(options = {}) {
|
|
261
|
+
if (!options.timerId) {
|
|
262
|
+
throw new Error('timerId is required');
|
|
263
|
+
}
|
|
264
|
+
const channel = normalizeChannel(options);
|
|
265
|
+
const payload = {
|
|
266
|
+
timerx: {
|
|
267
|
+
id: options.timerId
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const response = await this.publishMessage('DELETE', 'Appliance.Control.TimerX', payload);
|
|
271
|
+
|
|
272
|
+
const channelTimers = this._timerxStateByChannel.get(channel);
|
|
273
|
+
if (channelTimers && Array.isArray(channelTimers)) {
|
|
274
|
+
const filtered = channelTimers.filter(timer => timer.id !== options.timerId);
|
|
275
|
+
if (filtered.length === 0) {
|
|
276
|
+
this._timerxStateByChannel.delete(channel);
|
|
277
|
+
} else {
|
|
278
|
+
this._timerxStateByChannel.set(channel, filtered);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return response;
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Gets the cached timer state for the specified channel.
|
|
287
|
+
*
|
|
288
|
+
* Returns cached timers without making a request. Use {@link getTimerX} to fetch
|
|
289
|
+
* fresh timers from the device. Timers are automatically updated when commands are sent
|
|
290
|
+
* or push notifications are received.
|
|
291
|
+
*
|
|
292
|
+
* @param {number} [channel=0] - Channel to get timers for (default: 0)
|
|
293
|
+
* @returns {Array<import('../lib/model/states/timer-state').TimerState>|undefined} Array of cached timer states or undefined if not available
|
|
294
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
295
|
+
*/
|
|
296
|
+
getCachedTimerX(channel = 0) {
|
|
297
|
+
this.validateState();
|
|
298
|
+
return this._timerxStateByChannel.get(channel);
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Updates the cached timer state from timer data.
|
|
303
|
+
*
|
|
304
|
+
* Called automatically when TimerX push notifications are received. Updates existing
|
|
305
|
+
* timers by ID or adds new ones if they don't exist.
|
|
306
|
+
*
|
|
307
|
+
* @param {Object|Array} timerxData - Timer data (single object or array)
|
|
308
|
+
* @param {Object|Array} [digestData] - Optional digest data (unused)
|
|
309
|
+
* @private
|
|
310
|
+
*/
|
|
311
|
+
_updateTimerXState(timerxData, source = 'response') {
|
|
312
|
+
if (!timerxData) {return;}
|
|
313
|
+
|
|
314
|
+
const timerArray = Array.isArray(timerxData) ? timerxData : [timerxData];
|
|
315
|
+
|
|
316
|
+
for (const timerItem of timerArray) {
|
|
317
|
+
const channelIndex = timerItem.channel;
|
|
318
|
+
if (channelIndex === undefined || channelIndex === null) {continue;}
|
|
319
|
+
|
|
320
|
+
let channelTimers = this._timerxStateByChannel.get(channelIndex);
|
|
321
|
+
if (!channelTimers) {
|
|
322
|
+
channelTimers = [];
|
|
323
|
+
this._timerxStateByChannel.set(channelIndex, channelTimers);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const oldTimers = [...channelTimers];
|
|
327
|
+
const timerId = timerItem.id;
|
|
328
|
+
if (timerId) {
|
|
329
|
+
const existingIndex = channelTimers.findIndex(t => t.id === timerId);
|
|
330
|
+
if (existingIndex >= 0) {
|
|
331
|
+
channelTimers[existingIndex].update(timerItem);
|
|
332
|
+
} else {
|
|
333
|
+
const timerState = new TimerState(timerItem);
|
|
334
|
+
channelTimers.push(timerState);
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
const timerState = new TimerState(timerItem);
|
|
338
|
+
channelTimers.push(timerState);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const newTimers = [...channelTimers];
|
|
342
|
+
this.emit('stateChange', {
|
|
343
|
+
type: 'timer',
|
|
344
|
+
channel: channelIndex,
|
|
345
|
+
value: newTimers,
|
|
346
|
+
oldValue: oldTimers,
|
|
347
|
+
source,
|
|
348
|
+
timestamp: Date.now()
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Finds a timer by alias (name).
|
|
356
|
+
*
|
|
357
|
+
* @param {string} alias - Timer alias/name to search for
|
|
358
|
+
* @param {number} [channel=0] - Channel to search in
|
|
359
|
+
* @returns {Promise<import('../../model/states/timer-state').TimerState|null>} Timer state if found, null otherwise
|
|
360
|
+
* @example
|
|
361
|
+
* const timer = await device.findTimerByAlias('Morning Lights');
|
|
362
|
+
* if (timer) {
|
|
363
|
+
* console.log('Found timer:', timer.id);
|
|
364
|
+
* }
|
|
365
|
+
*/
|
|
366
|
+
async findTimerByAlias(options = {}) {
|
|
367
|
+
if (!options.alias) {
|
|
368
|
+
throw new Error('alias is required');
|
|
369
|
+
}
|
|
370
|
+
const channel = normalizeChannel(options);
|
|
371
|
+
const response = await this.getTimerX({ channel });
|
|
372
|
+
if (response && response.timerx && Array.isArray(response.timerx)) {
|
|
373
|
+
const timer = response.timerx.find(t => t.alias === options.alias);
|
|
374
|
+
if (timer) {
|
|
375
|
+
return new TimerState(timer);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Deletes a timer by alias (name).
|
|
383
|
+
*
|
|
384
|
+
* @param {Object} options - Delete options
|
|
385
|
+
* @param {string} options.alias - Timer alias/name to delete
|
|
386
|
+
* @param {number} [options.channel=0] - Channel to search in
|
|
387
|
+
* @returns {Promise<Object>} Response from the device
|
|
388
|
+
* @throws {Error} If timer with alias is not found
|
|
389
|
+
* @example
|
|
390
|
+
* await device.deleteTimerByAlias({alias: 'Morning Lights'});
|
|
391
|
+
*/
|
|
392
|
+
async deleteTimerByAlias(options = {}) {
|
|
393
|
+
if (!options.alias) {
|
|
394
|
+
throw new Error('alias is required');
|
|
395
|
+
}
|
|
396
|
+
const channel = normalizeChannel(options);
|
|
397
|
+
const timer = await this.findTimerByAlias({ alias: options.alias, channel });
|
|
398
|
+
if (!timer) {
|
|
399
|
+
throw new Error(`Timer with alias "${options.alias}" not found on channel ${channel}`);
|
|
400
|
+
}
|
|
401
|
+
return await this.deleteTimerX({ timerId: timer.id, channel });
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Enables a timer by alias (name).
|
|
406
|
+
*
|
|
407
|
+
* @param {Object} options - Enable options
|
|
408
|
+
* @param {string} options.alias - Timer alias/name to enable
|
|
409
|
+
* @param {number} [options.channel=0] - Channel to search in
|
|
410
|
+
* @returns {Promise<Object>} Response from the device
|
|
411
|
+
* @throws {Error} If timer with alias is not found
|
|
412
|
+
* @example
|
|
413
|
+
* await device.enableTimerByAlias({alias: 'Morning Lights'});
|
|
414
|
+
*/
|
|
415
|
+
async enableTimerByAlias(options = {}) {
|
|
416
|
+
if (!options.alias) {
|
|
417
|
+
throw new Error('alias is required');
|
|
418
|
+
}
|
|
419
|
+
const channel = normalizeChannel(options);
|
|
420
|
+
const response = await this.getTimerX({ channel });
|
|
421
|
+
if (!response || !response.timerx || !Array.isArray(response.timerx)) {
|
|
422
|
+
throw new Error(`Timer with alias "${options.alias}" not found on channel ${channel}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const timer = response.timerx.find(t => t.alias === options.alias);
|
|
426
|
+
if (!timer) {
|
|
427
|
+
throw new Error(`Timer with alias "${options.alias}" not found on channel ${channel}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Update timer with enabled state
|
|
431
|
+
const updatedTimer = {
|
|
432
|
+
...timer,
|
|
433
|
+
enable: 1
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
return await this.setTimerX({ timerx: updatedTimer });
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Disables a timer by alias (name).
|
|
441
|
+
*
|
|
442
|
+
* @param {Object} options - Disable options
|
|
443
|
+
* @param {string} options.alias - Timer alias/name to disable
|
|
444
|
+
* @param {number} [options.channel=0] - Channel to search in
|
|
445
|
+
* @returns {Promise<Object>} Response from the device
|
|
446
|
+
* @throws {Error} If timer with alias is not found
|
|
447
|
+
* @example
|
|
448
|
+
* await device.disableTimerByAlias({alias: 'Morning Lights'});
|
|
449
|
+
*/
|
|
450
|
+
async disableTimerByAlias(options = {}) {
|
|
451
|
+
if (!options.alias) {
|
|
452
|
+
throw new Error('alias is required');
|
|
453
|
+
}
|
|
454
|
+
const channel = normalizeChannel(options);
|
|
455
|
+
const response = await this.getTimerX({ channel });
|
|
456
|
+
if (!response || !response.timerx || !Array.isArray(response.timerx)) {
|
|
457
|
+
throw new Error(`Timer with alias "${options.alias}" not found on channel ${channel}`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const timer = response.timerx.find(t => t.alias === options.alias);
|
|
461
|
+
if (!timer) {
|
|
462
|
+
throw new Error(`Timer with alias "${options.alias}" not found on channel ${channel}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Update timer with disabled state
|
|
466
|
+
const updatedTimer = {
|
|
467
|
+
...timer,
|
|
468
|
+
enable: 0
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
return await this.setTimerX({ timerx: updatedTimer });
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Deletes all timers on a channel.
|
|
476
|
+
*
|
|
477
|
+
* @param {Object} [options={}] - Delete options
|
|
478
|
+
* @param {number} [options.channel=0] - Channel to delete timers from
|
|
479
|
+
* @returns {Promise<Array<Object>>} Array of delete responses
|
|
480
|
+
* @example
|
|
481
|
+
* const results = await device.deleteAllTimers({channel: 0});
|
|
482
|
+
* console.log(`Deleted ${results.length} timers`);
|
|
483
|
+
*/
|
|
484
|
+
async deleteAllTimers(options = {}) {
|
|
485
|
+
const channel = normalizeChannel(options);
|
|
486
|
+
const response = await this.getTimerX({ channel });
|
|
487
|
+
if (!response || !response.timerx || !Array.isArray(response.timerx) || response.timerx.length === 0) {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const deletePromises = response.timerx.map(timer => {
|
|
492
|
+
if (timer.id) {
|
|
493
|
+
return this.deleteTimerX({ timerId: timer.id, channel }).catch(error => {
|
|
494
|
+
if (this.log) {
|
|
495
|
+
this.log(`Failed to delete timer ${timer.id}: ${error.message}`);
|
|
496
|
+
}
|
|
497
|
+
return null;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return Promise.resolve(null);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const results = await Promise.all(deletePromises);
|
|
504
|
+
return results.filter(r => r !== null);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|