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,223 @@
1
+ 'use strict';
2
+
3
+ const ToggleState = require('../../model/states/toggle-state');
4
+ const { normalizeChannel, validateRequired } = require('../../utilities/options');
5
+
6
+ /**
7
+ * Toggle feature module.
8
+ * Provides control over device on/off state for single-channel and multi-channel devices.
9
+ */
10
+ module.exports = {
11
+ /**
12
+ * Controls the toggle state (on/off) for single-channel devices.
13
+ *
14
+ * @param {Object} options - Toggle options
15
+ * @param {boolean} options.onoff - True to turn on, false to turn off
16
+ * @returns {Promise<Object>} Response from the device
17
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
18
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
19
+ */
20
+ async setToggle(options = {}) {
21
+ validateRequired(options, ['onoff']);
22
+ const payload = { 'toggle': { 'onoff': options.onoff ? 1 : 0 } };
23
+ const response = await this.publishMessage('SET', 'Appliance.Control.Toggle', payload);
24
+
25
+ if (response && response.toggle) {
26
+ this._updateToggleState(response.toggle, 'response');
27
+ } else {
28
+ this._updateToggleState({ channel: 0, onoff: options.onoff ? 1 : 0 }, 'response');
29
+ }
30
+
31
+ return response;
32
+ },
33
+
34
+ /**
35
+ * Controls the toggle state for a specific channel (on/off).
36
+ *
37
+ * @param {Object} options - Toggle options
38
+ * @param {number} [options.channel=0] - Channel to control (default: 0)
39
+ * @param {boolean} options.onoff - True to turn on, false to turn off
40
+ * @returns {Promise<Object>} Response from the device
41
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
42
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
43
+ */
44
+ async setToggleX(options = {}) {
45
+ validateRequired(options, ['onoff']);
46
+ const channel = normalizeChannel(options);
47
+ const payload = { 'togglex': { channel, 'onoff': options.onoff ? 1 : 0 } };
48
+ const response = await this.publishMessage('SET', 'Appliance.Control.ToggleX', payload);
49
+
50
+ if (response && response.togglex) {
51
+ this._updateToggleState(response.togglex, 'response');
52
+ this._lastFullUpdateTimestamp = Date.now();
53
+ } else {
54
+ this._updateToggleState({ channel, onoff: options.onoff ? 1 : 0 }, 'response');
55
+ this._lastFullUpdateTimestamp = Date.now();
56
+ }
57
+
58
+ return response;
59
+ },
60
+
61
+ /**
62
+ * Gets the current toggle state from the device.
63
+ *
64
+ * @param {Object} [options={}] - Get options
65
+ * @param {number} [options.channel=0] - Channel to get state for (default: 0, use 65535 or 0xffff for all channels)
66
+ * @returns {Promise<Object>} Response containing toggle state
67
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
68
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
69
+ */
70
+ async getToggleState(options = {}) {
71
+ const channel = normalizeChannel(options);
72
+ const payload = { 'togglex': { channel } };
73
+ const response = await this.publishMessage('GET', 'Appliance.Control.ToggleX', payload);
74
+ if (response && response.togglex) {
75
+ this._updateToggleState(response.togglex, 'response');
76
+ this._lastFullUpdateTimestamp = Date.now();
77
+ }
78
+ return response;
79
+ },
80
+
81
+ /**
82
+ * Gets the cached toggle state for the specified channel.
83
+ *
84
+ * @param {number} [channel=0] - Channel to get state for (default: 0)
85
+ * @returns {Object|undefined} Cached toggle state or undefined if not available
86
+ * @throws {Error} If state has not been initialized (call refreshState() first)
87
+ */
88
+ getCachedToggleState(channel = 0) {
89
+ this.validateState();
90
+ return this._toggleStateByChannel.get(channel);
91
+ },
92
+
93
+ /**
94
+ * Gets all cached toggle states for all channels.
95
+ *
96
+ * Returns a read-only copy of the internal state map. Modifications to the returned
97
+ * Map will not affect the internal state.
98
+ *
99
+ * @returns {Map<number, ToggleState>} Map of channel numbers to toggle states, or empty Map if no states cached
100
+ * @throws {Error} If state has not been initialized (call refreshState() first)
101
+ */
102
+ getAllCachedToggleStates() {
103
+ this.validateState();
104
+ return new Map(this._toggleStateByChannel);
105
+ },
106
+
107
+ /**
108
+ * Checks if the device is on for the specified channel.
109
+ *
110
+ * @param {number} [channel=0] - Channel to check (default: 0)
111
+ * @returns {boolean|undefined} True if on, false if off, undefined if not available
112
+ * @throws {Error} If state has not been initialized (call refreshState() first)
113
+ */
114
+ isOn(channel = 0) {
115
+ this.validateState();
116
+ const toggleState = this._toggleStateByChannel.get(channel);
117
+ if (toggleState) {
118
+ return toggleState.isOn;
119
+ }
120
+ return undefined;
121
+ },
122
+
123
+ /**
124
+ * Turns on the device for the specified channel.
125
+ *
126
+ * Automatically selects the appropriate toggle method (ToggleX or Toggle) based on
127
+ * device capabilities.
128
+ *
129
+ * @param {Object} [options={}] - Turn on options
130
+ * @param {number} [options.channel=0] - Channel to control (default: 0)
131
+ * @returns {Promise<Object>} Response from the device
132
+ * @throws {import('../lib/errors/errors').UnknownDeviceTypeError} If device does not support Toggle or ToggleX
133
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
134
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
135
+ */
136
+ async turnOn(options = {}) {
137
+ if (this._abilities) {
138
+ const hasToggleX = this._abilities['Appliance.Control.ToggleX'];
139
+ const hasToggle = this._abilities['Appliance.Control.Toggle'];
140
+
141
+ if (hasToggleX) {
142
+ return await this.setToggleX({ ...options, onoff: true });
143
+ } else if (hasToggle) {
144
+ return await this.setToggle({ onoff: true });
145
+ }
146
+ }
147
+ const { UnknownDeviceTypeError } = require('../../model/exception');
148
+ throw new UnknownDeviceTypeError('Device does not support Toggle or ToggleX', this.deviceType);
149
+ },
150
+
151
+ /**
152
+ * Turns off the device for the specified channel.
153
+ *
154
+ * Automatically selects the appropriate toggle method (ToggleX or Toggle) based on
155
+ * device capabilities.
156
+ *
157
+ * @param {Object} [options={}] - Turn off options
158
+ * @param {number} [options.channel=0] - Channel to control (default: 0)
159
+ * @returns {Promise<Object>} Response from the device
160
+ * @throws {import('../lib/errors/errors').UnknownDeviceTypeError} If device does not support Toggle or ToggleX
161
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
162
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
163
+ */
164
+ async turnOff(options = {}) {
165
+ if (this._abilities) {
166
+ const hasToggleX = this._abilities['Appliance.Control.ToggleX'];
167
+ const hasToggle = this._abilities['Appliance.Control.Toggle'];
168
+
169
+ if (hasToggleX) {
170
+ return await this.setToggleX({ ...options, onoff: false });
171
+ } else if (hasToggle) {
172
+ return await this.setToggle({ onoff: false });
173
+ }
174
+ }
175
+ const { UnknownDeviceTypeError } = require('../../model/exception');
176
+ throw new UnknownDeviceTypeError('Device does not support Toggle or ToggleX', this.deviceType);
177
+ },
178
+
179
+ /**
180
+ * Updates the cached toggle state from toggle data.
181
+ *
182
+ * Called automatically when ToggleX push notifications are received or System.All
183
+ * digest is processed. Handles both single objects and arrays of toggle data.
184
+ *
185
+ * @param {Object|Array} toggleData - Toggle data (single object or array)
186
+ * @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
187
+ * @private
188
+ */
189
+ _updateToggleState(toggleData, source = 'response') {
190
+ if (!toggleData) {return;}
191
+
192
+ const toggleArray = Array.isArray(toggleData) ? toggleData : [toggleData];
193
+
194
+ for (const toggleItem of toggleArray) {
195
+ const channelIndex = toggleItem.channel;
196
+ if (channelIndex === undefined || channelIndex === null) {continue;}
197
+
198
+ const oldState = this._toggleStateByChannel.get(channelIndex);
199
+ const oldValue = oldState ? oldState.isOn : undefined;
200
+
201
+ let state = this._toggleStateByChannel.get(channelIndex);
202
+ if (!state) {
203
+ state = new ToggleState(toggleItem);
204
+ this._toggleStateByChannel.set(channelIndex, state);
205
+ } else {
206
+ state.update(toggleItem);
207
+ }
208
+
209
+ const newValue = state.isOn;
210
+ if (oldValue !== newValue) {
211
+ this.emit('stateChange', {
212
+ type: 'toggle',
213
+ channel: channelIndex,
214
+ value: newValue,
215
+ oldValue,
216
+ source,
217
+ timestamp: Date.now()
218
+ });
219
+ }
220
+ }
221
+ }
222
+ };
223
+
@@ -0,0 +1,333 @@
1
+ 'use strict';
2
+
3
+ const TriggerState = require('../../model/states/trigger-state');
4
+ const { normalizeChannel } = require('../../utilities/options');
5
+ const { createTrigger } = require('../../utilities/trigger');
6
+
7
+ /**
8
+ * Trigger feature module.
9
+ * Provides control over device triggers that can fire on/off actions based on sensor conditions.
10
+ */
11
+ module.exports = {
12
+ /**
13
+ * Gets trigger information for a specific channel.
14
+ *
15
+ * Use {@link getCachedTriggerX} to get cached triggers without making a request.
16
+ *
17
+ * @param {Object} [options={}] - Get options
18
+ * @param {number} [options.channel=0] - Channel to get triggers for (default: 0, use 65535 or 0xffff for all channels)
19
+ * @returns {Promise<Object>} Response containing trigger data with `triggerx` array
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 getTriggerX(options = {}) {
24
+ const channel = normalizeChannel(options);
25
+ const payload = {
26
+ triggerx: {
27
+ channel
28
+ }
29
+ };
30
+ const response = await this.publishMessage('GET', 'Appliance.Control.TriggerX', payload);
31
+ if (response && response.triggerx) {
32
+ this._updateTriggerXState(response.triggerx, 'response');
33
+ }
34
+ return response;
35
+ },
36
+
37
+ /**
38
+ * Controls a trigger (creates or updates).
39
+ *
40
+ * Accepts either a user-friendly format (which will be converted via createTrigger) or
41
+ * a raw triggerx object. If a trigger ID is provided, updates the existing trigger. Otherwise,
42
+ * creates a new trigger with a generated ID.
43
+ *
44
+ * @param {Object} options - Trigger options
45
+ * @param {Object} [options.triggerx] - Raw trigger configuration object (if provided, used directly)
46
+ * @param {string} [options.triggerx.id] - Trigger ID (required for updates, generated for new triggers)
47
+ * @param {number} [options.triggerx.channel] - Channel the trigger belongs to
48
+ * @param {string|number} [options.duration] - Duration as seconds (number), "30m", "1h", or "HH:MM:SS" format (user-friendly format)
49
+ * @param {Array<string|number>|number} [options.days] - Days of week (array of names/numbers) or week bitmask (user-friendly format)
50
+ * @param {string} [options.alias] - Trigger name/alias (user-friendly format)
51
+ * @param {number} [options.channel] - Channel number (user-friendly format)
52
+ * @param {boolean} [options.enabled] - Whether trigger is enabled (user-friendly format)
53
+ * @param {string} [options.id] - Trigger ID (user-friendly format)
54
+ * @returns {Promise<Object>} Response from the device containing the updated trigger
55
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
56
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
57
+ */
58
+ async setTriggerX(options = {}) {
59
+ let triggerx;
60
+
61
+ if (options.triggerx) {
62
+ triggerx = options.triggerx;
63
+ } else {
64
+ triggerx = createTrigger(options);
65
+ }
66
+
67
+ const payload = { triggerx };
68
+ const response = await this.publishMessage('SET', 'Appliance.Control.TriggerX', payload);
69
+ if (response && response.triggerx) {
70
+ this._updateTriggerXState(response.triggerx);
71
+ } else if (triggerx) {
72
+ this._updateTriggerXState([triggerx]);
73
+ }
74
+ return response;
75
+ },
76
+
77
+ /**
78
+ * Deletes a trigger by ID.
79
+ *
80
+ * @param {Object} options - Delete options
81
+ * @param {string} options.triggerId - Trigger ID to delete
82
+ * @param {number} [options.channel=0] - Channel the trigger belongs to (default: 0)
83
+ * @returns {Promise<Object>} Response from the device
84
+ * @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
85
+ * @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
86
+ */
87
+ async deleteTriggerX(options = {}) {
88
+ if (!options.triggerId) {
89
+ throw new Error('triggerId is required');
90
+ }
91
+ const channel = normalizeChannel(options);
92
+ const payload = {
93
+ triggerx: {
94
+ id: options.triggerId
95
+ }
96
+ };
97
+ const response = await this.publishMessage('DELETE', 'Appliance.Control.TriggerX', payload);
98
+
99
+ const channelTriggers = this._triggerxStateByChannel.get(channel);
100
+ if (channelTriggers && Array.isArray(channelTriggers)) {
101
+ const filtered = channelTriggers.filter(trigger => trigger.id !== options.triggerId);
102
+ if (filtered.length === 0) {
103
+ this._triggerxStateByChannel.delete(channel);
104
+ } else {
105
+ this._triggerxStateByChannel.set(channel, filtered);
106
+ }
107
+ }
108
+
109
+ return response;
110
+ },
111
+
112
+ /**
113
+ * Gets the cached trigger state for the specified channel.
114
+ *
115
+ * Returns cached triggers without making a request. Use {@link getTriggerX} to fetch
116
+ * fresh triggers from the device. Triggers are automatically updated when commands are sent
117
+ * or push notifications are received.
118
+ *
119
+ * @param {number} [channel=0] - Channel to get triggers for (default: 0)
120
+ * @returns {Array<import('../lib/model/states/trigger-state').TriggerState>|undefined} Array of cached trigger states or undefined if not available
121
+ * @throws {Error} If state has not been initialized (call refreshState() first)
122
+ */
123
+ getCachedTriggerX(channel = 0) {
124
+ this.validateState();
125
+ return this._triggerxStateByChannel.get(channel);
126
+ },
127
+
128
+ /**
129
+ * Updates the cached trigger state from trigger data.
130
+ *
131
+ * Called automatically when TriggerX push notifications are received. Updates existing
132
+ * triggers by ID or adds new ones if they don't exist.
133
+ *
134
+ * @param {Object|Array} triggerxData - Trigger data (single object or array)
135
+ * @param {Object|Array} [digestData] - Optional digest data (unused)
136
+ * @private
137
+ */
138
+ _updateTriggerXState(triggerxData, source = 'response') {
139
+ if (!triggerxData) {return;}
140
+
141
+ const triggerArray = Array.isArray(triggerxData) ? triggerxData : [triggerxData];
142
+
143
+ for (const triggerItem of triggerArray) {
144
+ const channelIndex = triggerItem.channel;
145
+ if (channelIndex === undefined || channelIndex === null) {continue;}
146
+
147
+ let channelTriggers = this._triggerxStateByChannel.get(channelIndex);
148
+ if (!channelTriggers) {
149
+ channelTriggers = [];
150
+ this._triggerxStateByChannel.set(channelIndex, channelTriggers);
151
+ }
152
+
153
+ const oldTriggers = [...channelTriggers];
154
+ const triggerId = triggerItem.id;
155
+ if (triggerId) {
156
+ const existingIndex = channelTriggers.findIndex(t => t.id === triggerId);
157
+ if (existingIndex >= 0) {
158
+ channelTriggers[existingIndex].update(triggerItem);
159
+ } else {
160
+ const triggerState = new TriggerState(triggerItem);
161
+ channelTriggers.push(triggerState);
162
+ }
163
+ } else {
164
+ const triggerState = new TriggerState(triggerItem);
165
+ channelTriggers.push(triggerState);
166
+ }
167
+
168
+ const newTriggers = [...channelTriggers];
169
+ this.emit('stateChange', {
170
+ type: 'trigger',
171
+ channel: channelIndex,
172
+ value: newTriggers,
173
+ oldValue: oldTriggers,
174
+ source,
175
+ timestamp: Date.now()
176
+ });
177
+ }
178
+ },
179
+
180
+ /**
181
+ * Finds a trigger by alias (name).
182
+ *
183
+ * @param {string} alias - Trigger alias/name to search for
184
+ * @param {number} [channel=0] - Channel to search in
185
+ * @returns {Promise<import('../../model/states/trigger-state').TriggerState|null>} Trigger state if found, null otherwise
186
+ * @example
187
+ * const trigger = await device.findTriggerByAlias('Auto-off after 30 minutes');
188
+ * if (trigger) {
189
+ * console.log('Found trigger:', trigger.id);
190
+ * }
191
+ */
192
+ async findTriggerByAlias(options = {}) {
193
+ if (!options.alias) {
194
+ throw new Error('alias is required');
195
+ }
196
+ const channel = normalizeChannel(options);
197
+ const response = await this.getTriggerX({ channel });
198
+ if (response && response.triggerx && Array.isArray(response.triggerx)) {
199
+ const trigger = response.triggerx.find(t => t.alias === options.alias);
200
+ if (trigger) {
201
+ return new TriggerState(trigger);
202
+ }
203
+ }
204
+ return null;
205
+ },
206
+
207
+ /**
208
+ * Deletes a trigger by alias (name).
209
+ *
210
+ * @param {Object} options - Delete options
211
+ * @param {string} options.alias - Trigger alias/name to delete
212
+ * @param {number} [options.channel=0] - Channel to search in
213
+ * @returns {Promise<Object>} Response from the device
214
+ * @throws {Error} If trigger with alias is not found
215
+ * @example
216
+ * await device.deleteTriggerByAlias({alias: 'Auto-off after 30 minutes'});
217
+ */
218
+ async deleteTriggerByAlias(options = {}) {
219
+ if (!options.alias) {
220
+ throw new Error('alias is required');
221
+ }
222
+ const channel = normalizeChannel(options);
223
+ const trigger = await this.findTriggerByAlias({ alias: options.alias, channel });
224
+ if (!trigger) {
225
+ throw new Error(`Trigger with alias "${options.alias}" not found on channel ${channel}`);
226
+ }
227
+ return await this.deleteTriggerX({ triggerId: trigger.id, channel });
228
+ },
229
+
230
+ /**
231
+ * Enables a trigger by alias (name).
232
+ *
233
+ * @param {Object} options - Enable options
234
+ * @param {string} options.alias - Trigger alias/name to enable
235
+ * @param {number} [options.channel=0] - Channel to search in
236
+ * @returns {Promise<Object>} Response from the device
237
+ * @throws {Error} If trigger with alias is not found
238
+ * @example
239
+ * await device.enableTriggerByAlias({alias: 'Auto-off after 30 minutes'});
240
+ */
241
+ async enableTriggerByAlias(options = {}) {
242
+ if (!options.alias) {
243
+ throw new Error('alias is required');
244
+ }
245
+ const channel = normalizeChannel(options);
246
+ const response = await this.getTriggerX({ channel });
247
+ if (!response || !response.triggerx || !Array.isArray(response.triggerx)) {
248
+ throw new Error(`Trigger with alias "${options.alias}" not found on channel ${channel}`);
249
+ }
250
+
251
+ const trigger = response.triggerx.find(t => t.alias === options.alias);
252
+ if (!trigger) {
253
+ throw new Error(`Trigger with alias "${options.alias}" not found on channel ${channel}`);
254
+ }
255
+
256
+ // Update trigger with enabled state
257
+ const updatedTrigger = {
258
+ ...trigger,
259
+ enable: 1
260
+ };
261
+
262
+ return await this.setTriggerX({ triggerx: updatedTrigger });
263
+ },
264
+
265
+ /**
266
+ * Disables a trigger by alias (name).
267
+ *
268
+ * @param {Object} options - Disable options
269
+ * @param {string} options.alias - Trigger alias/name to disable
270
+ * @param {number} [options.channel=0] - Channel to search in
271
+ * @returns {Promise<Object>} Response from the device
272
+ * @throws {Error} If trigger with alias is not found
273
+ * @example
274
+ * await device.disableTriggerByAlias({alias: 'Auto-off after 30 minutes'});
275
+ */
276
+ async disableTriggerByAlias(options = {}) {
277
+ if (!options.alias) {
278
+ throw new Error('alias is required');
279
+ }
280
+ const channel = normalizeChannel(options);
281
+ const response = await this.getTriggerX({ channel });
282
+ if (!response || !response.triggerx || !Array.isArray(response.triggerx)) {
283
+ throw new Error(`Trigger with alias "${options.alias}" not found on channel ${channel}`);
284
+ }
285
+
286
+ const trigger = response.triggerx.find(t => t.alias === options.alias);
287
+ if (!trigger) {
288
+ throw new Error(`Trigger with alias "${options.alias}" not found on channel ${channel}`);
289
+ }
290
+
291
+ // Update trigger with disabled state
292
+ const updatedTrigger = {
293
+ ...trigger,
294
+ enable: 0
295
+ };
296
+
297
+ return await this.setTriggerX({ triggerx: updatedTrigger });
298
+ },
299
+
300
+ /**
301
+ * Deletes all triggers on a channel.
302
+ *
303
+ * @param {Object} [options={}] - Delete options
304
+ * @param {number} [options.channel=0] - Channel to delete triggers from
305
+ * @returns {Promise<Array<Object>>} Array of delete responses
306
+ * @example
307
+ * const results = await device.deleteAllTriggers({channel: 0});
308
+ * console.log(`Deleted ${results.length} triggers`);
309
+ */
310
+ async deleteAllTriggers(options = {}) {
311
+ const channel = normalizeChannel(options);
312
+ const response = await this.getTriggerX({ channel });
313
+ if (!response || !response.triggerx || !Array.isArray(response.triggerx) || response.triggerx.length === 0) {
314
+ return [];
315
+ }
316
+
317
+ const deletePromises = response.triggerx.map(trigger => {
318
+ if (trigger.id) {
319
+ return this.deleteTriggerX({ triggerId: trigger.id, channel }).catch(error => {
320
+ if (this.log) {
321
+ this.log(`Failed to delete trigger ${trigger.id}: ${error.message}`);
322
+ }
323
+ return null;
324
+ });
325
+ }
326
+ return Promise.resolve(null);
327
+ });
328
+
329
+ const results = await Promise.all(deletePromises);
330
+ return results.filter(r => r !== null);
331
+ }
332
+ };
333
+