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,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
+