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