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,804 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ThermostatState = require('../../model/states/thermostat-state');
|
|
4
|
+
const { normalizeChannel } = require('../../utilities/options');
|
|
5
|
+
const { buildStateChanges } = require('../../utilities/state-changes');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Thermostat feature module.
|
|
9
|
+
* Provides control over thermostat mode, temperature settings, schedules, and various configuration options.
|
|
10
|
+
*/
|
|
11
|
+
module.exports = {
|
|
12
|
+
/**
|
|
13
|
+
* Controls the thermostat mode.
|
|
14
|
+
*
|
|
15
|
+
* Supports both ThermostatMode enum objects and numeric values. Temperature values are automatically
|
|
16
|
+
* converted from Celsius to device units and rounded to the nearest 0.5°C increment.
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} options - Thermostat mode options
|
|
19
|
+
* @param {number} [options.channel=0] - Channel to control (default: 0)
|
|
20
|
+
* @param {number|import('../lib/enums').ThermostatMode} [options.mode] - Thermostat mode (from ThermostatMode enum or numeric value)
|
|
21
|
+
* @param {number} [options.onoff] - On/off state (0=off, 1=on)
|
|
22
|
+
* @param {number} [options.heatTemperature] - Heat temperature in Celsius (will be converted to device units)
|
|
23
|
+
* @param {number} [options.coolTemperature] - Cool temperature in Celsius (will be converted to device units)
|
|
24
|
+
* @param {number} [options.ecoTemperature] - Eco temperature in Celsius (will be converted to device units)
|
|
25
|
+
* @param {number} [options.manualTemperature] - Manual temperature in Celsius (will be converted to device units)
|
|
26
|
+
* @param {boolean} [options.partialUpdate=false] - If true, fetches current state and merges with provided options
|
|
27
|
+
* @returns {Promise<Object>} Response from the device
|
|
28
|
+
* @throws {import('../lib/errors/errors').CommandError} If mode value is invalid or temperature is out of range
|
|
29
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
30
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
31
|
+
*/
|
|
32
|
+
async setThermostatMode(options = {}) {
|
|
33
|
+
const channel = normalizeChannel(options);
|
|
34
|
+
let processedModeData = { ...options };
|
|
35
|
+
|
|
36
|
+
if (options.partialUpdate) {
|
|
37
|
+
try {
|
|
38
|
+
const currentResponse = await this.getThermostatMode({ channel });
|
|
39
|
+
if (currentResponse && currentResponse.mode && Array.isArray(currentResponse.mode) && currentResponse.mode.length > 0) {
|
|
40
|
+
const currentState = currentResponse.mode[0];
|
|
41
|
+
processedModeData = { ...currentState, ...processedModeData };
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// If fetch fails, continue with provided options only
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
processedModeData.channel = channel;
|
|
49
|
+
|
|
50
|
+
// Mode must be a number
|
|
51
|
+
if (processedModeData.mode !== undefined && processedModeData.mode !== null && typeof processedModeData.mode !== 'number') {
|
|
52
|
+
processedModeData.mode = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (processedModeData.heatTemperature !== undefined) {
|
|
56
|
+
processedModeData.heatTemp = this._alignThermostatTemperature(processedModeData.heatTemperature, channel);
|
|
57
|
+
delete processedModeData.heatTemperature;
|
|
58
|
+
}
|
|
59
|
+
if (processedModeData.coolTemperature !== undefined) {
|
|
60
|
+
processedModeData.coolTemp = this._alignThermostatTemperature(processedModeData.coolTemperature, channel);
|
|
61
|
+
delete processedModeData.coolTemperature;
|
|
62
|
+
}
|
|
63
|
+
if (processedModeData.ecoTemperature !== undefined) {
|
|
64
|
+
processedModeData.ecoTemp = this._alignThermostatTemperature(processedModeData.ecoTemperature, channel);
|
|
65
|
+
delete processedModeData.ecoTemperature;
|
|
66
|
+
}
|
|
67
|
+
if (processedModeData.manualTemperature !== undefined) {
|
|
68
|
+
processedModeData.manualTemp = this._alignThermostatTemperature(processedModeData.manualTemperature, channel);
|
|
69
|
+
delete processedModeData.manualTemperature;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
delete processedModeData.partialUpdate;
|
|
73
|
+
|
|
74
|
+
const payload = { 'mode': [processedModeData] };
|
|
75
|
+
const response = await this.publishMessage('SET', 'Appliance.Control.Thermostat.Mode', payload);
|
|
76
|
+
if (response && response.mode) {
|
|
77
|
+
this._updateThermostatMode(response.mode, 'response');
|
|
78
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
79
|
+
}
|
|
80
|
+
return response;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Controls the thermostat mode B.
|
|
85
|
+
*
|
|
86
|
+
* Mode B is an alternative mode system used by some thermostat models. Supports both ThermostatModeBState
|
|
87
|
+
* enum objects and numeric values. Throws an error if the device does not support the ModeB namespace.
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} options - Thermostat mode B options
|
|
90
|
+
* @param {number} [options.channel=0] - Channel to control (default: 0)
|
|
91
|
+
* @param {number|import('../lib/enums').ThermostatModeBState} [options.state] - Mode B state (from ThermostatModeBState enum or numeric value)
|
|
92
|
+
* @returns {Promise<Object>} Response from the device
|
|
93
|
+
* @throws {import('../lib/errors/errors').CommandError} If the device does not support the ModeB namespace or state value is invalid
|
|
94
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
95
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
96
|
+
*/
|
|
97
|
+
async setThermostatModeB(options = {}) {
|
|
98
|
+
const channel = normalizeChannel(options);
|
|
99
|
+
if (!this._abilities || !this._abilities['Appliance.Control.Thermostat.ModeB']) {
|
|
100
|
+
const { CommandError } = require('../../model/exception');
|
|
101
|
+
throw new CommandError(
|
|
102
|
+
'Device does not support Appliance.Control.Thermostat.ModeB namespace',
|
|
103
|
+
{ namespace: 'Appliance.Control.Thermostat.ModeB', channel, options },
|
|
104
|
+
this.uuid
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const processedModeData = { ...options };
|
|
109
|
+
processedModeData.channel = channel;
|
|
110
|
+
|
|
111
|
+
// State must be a number
|
|
112
|
+
if (processedModeData.state !== undefined && processedModeData.state !== null && typeof processedModeData.state !== 'number') {
|
|
113
|
+
processedModeData.state = 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const payload = { 'modeB': [processedModeData] };
|
|
117
|
+
const response = await this.publishMessage('SET', 'Appliance.Control.Thermostat.ModeB', payload);
|
|
118
|
+
if (response && response.modeB) {
|
|
119
|
+
this._updateThermostatModeB(response.modeB, 'response');
|
|
120
|
+
}
|
|
121
|
+
return response;
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Controls the thermostat window opened status.
|
|
126
|
+
*
|
|
127
|
+
* Used to inform the thermostat when a window is opened or closed, which may affect heating/cooling behavior.
|
|
128
|
+
*
|
|
129
|
+
* @param {Object} options - Window opened options
|
|
130
|
+
* @param {number} [options.channel=0] - Channel to control (default: 0)
|
|
131
|
+
* @param {boolean} options.windowOpened - True if window is opened, false if closed
|
|
132
|
+
* @returns {Promise<Object>} Response from the device
|
|
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 setThermostatWindowOpened(options = {}) {
|
|
137
|
+
const channel = normalizeChannel(options);
|
|
138
|
+
if (options.windowOpened === undefined) {
|
|
139
|
+
throw new Error('windowOpened is required');
|
|
140
|
+
}
|
|
141
|
+
const payload = { 'windowOpened': [{ channel, 'status': options.windowOpened ? 1 : 0 }] };
|
|
142
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.WindowOpened', payload);
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gets the current thermostat mode from the device.
|
|
147
|
+
*
|
|
148
|
+
* Use {@link getCachedThermostatState} to get cached state without making a request.
|
|
149
|
+
*
|
|
150
|
+
* @param {Object} [options={}] - Get options
|
|
151
|
+
* @param {number} [options.channel=0] - Channel to get mode for (default: 0)
|
|
152
|
+
* @returns {Promise<Object>} Response containing thermostat mode
|
|
153
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
154
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
155
|
+
*/
|
|
156
|
+
async getThermostatMode(options = {}) {
|
|
157
|
+
const channel = normalizeChannel(options);
|
|
158
|
+
const payload = { 'mode': [{ channel }] };
|
|
159
|
+
const response = await this.publishMessage('GET', 'Appliance.Control.Thermostat.Mode', payload);
|
|
160
|
+
if (response && response.mode) {
|
|
161
|
+
this._updateThermostatMode(response.mode, 'response');
|
|
162
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
163
|
+
}
|
|
164
|
+
return response;
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Gets the current thermostat mode B from the device.
|
|
169
|
+
*
|
|
170
|
+
* Throws an error if the device does not support the ModeB namespace. Use {@link getCachedThermostatModeBState}
|
|
171
|
+
* to get cached state without making a request.
|
|
172
|
+
*
|
|
173
|
+
* @param {Object} [options={}] - Get options
|
|
174
|
+
* @param {number} [options.channel=0] - Channel to get mode B for (default: 0)
|
|
175
|
+
* @returns {Promise<Object>} Response containing thermostat mode B
|
|
176
|
+
* @throws {import('../lib/errors/errors').CommandError} If the device does not support the ModeB namespace
|
|
177
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
178
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
179
|
+
*/
|
|
180
|
+
async getThermostatModeB(options = {}) {
|
|
181
|
+
const channel = normalizeChannel(options);
|
|
182
|
+
if (!this._abilities || !this._abilities['Appliance.Control.Thermostat.ModeB']) {
|
|
183
|
+
const { CommandError } = require('../../model/exception');
|
|
184
|
+
throw new CommandError(
|
|
185
|
+
'Device does not support Appliance.Control.Thermostat.ModeB namespace',
|
|
186
|
+
{ namespace: 'Appliance.Control.Thermostat.ModeB', channel },
|
|
187
|
+
this.uuid
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const payload = { 'modeB': [{ channel }] };
|
|
192
|
+
const response = await this.publishMessage('GET', 'Appliance.Control.Thermostat.ModeB', payload);
|
|
193
|
+
if (response && response.modeB) {
|
|
194
|
+
this._updateThermostatModeB(response.modeB, 'response');
|
|
195
|
+
this._lastFullUpdateTimestamp = Date.now();
|
|
196
|
+
}
|
|
197
|
+
return response;
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Gets the thermostat window opened status from the device.
|
|
202
|
+
*
|
|
203
|
+
* @param {Object} [options={}] - Get options
|
|
204
|
+
* @param {number} [options.channel=0] - Channel to get window opened status for (default: 0)
|
|
205
|
+
* @returns {Promise<Object>} Response containing window opened status
|
|
206
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
207
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
208
|
+
*/
|
|
209
|
+
async getThermostatWindowOpened(options = {}) {
|
|
210
|
+
const channel = normalizeChannel(options);
|
|
211
|
+
const payload = { 'windowOpened': [{ channel }] };
|
|
212
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.WindowOpened', payload);
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Gets the thermostat schedule from the device.
|
|
217
|
+
* @param {Object} [options={}] - Get options
|
|
218
|
+
* @param {number} [options.channel=0] - Channel to get schedule for (default: 0)
|
|
219
|
+
* @returns {Promise<Object>} Response containing thermostat schedule
|
|
220
|
+
*/
|
|
221
|
+
async getThermostatSchedule(options = {}) {
|
|
222
|
+
const channel = normalizeChannel(options);
|
|
223
|
+
const payload = {
|
|
224
|
+
schedule: [{
|
|
225
|
+
channel
|
|
226
|
+
}]
|
|
227
|
+
};
|
|
228
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.Schedule', payload);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Controls (sets) the thermostat schedule.
|
|
233
|
+
* @param {Object} scheduleData - Schedule data object (array of schedule items)
|
|
234
|
+
* @param {number|null} timeout - Optional timeout in milliseconds
|
|
235
|
+
* @returns {Promise<Object>} Response from the device
|
|
236
|
+
*/
|
|
237
|
+
async setThermostatSchedule(scheduleData) {
|
|
238
|
+
const payload = { schedule: Array.isArray(scheduleData) ? scheduleData : [scheduleData] };
|
|
239
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.Schedule', payload);
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Gets the thermostat timer configuration from the device.
|
|
244
|
+
* @param {Object} [options={}] - Get options
|
|
245
|
+
* @param {number} [options.channel=0] - Channel to get timer for (default: 0)
|
|
246
|
+
* @returns {Promise<Object>} Response containing thermostat timer data
|
|
247
|
+
*/
|
|
248
|
+
async getThermostatTimer(options = {}) {
|
|
249
|
+
const channel = normalizeChannel(options);
|
|
250
|
+
const payload = {
|
|
251
|
+
timer: [{
|
|
252
|
+
channel
|
|
253
|
+
}]
|
|
254
|
+
};
|
|
255
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.Timer', payload);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Controls (sets) the thermostat timer configuration.
|
|
260
|
+
* @param {Object} timerData - Timer data object (array of timer items)
|
|
261
|
+
* @param {number|null} timeout - Optional timeout in milliseconds
|
|
262
|
+
* @returns {Promise<Object>} Response from the device
|
|
263
|
+
*/
|
|
264
|
+
async setThermostatTimer(timerData) {
|
|
265
|
+
const payload = { timer: Array.isArray(timerData) ? timerData : [timerData] };
|
|
266
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.Timer', payload);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Acknowledges a thermostat alarm (device-initiated SET).
|
|
271
|
+
*
|
|
272
|
+
* Alarm events are typically initiated by the device via SET. This method sends a SETACK response
|
|
273
|
+
* to acknowledge receipt of the alarm event.
|
|
274
|
+
*
|
|
275
|
+
* @param {Object} [options={}] - Acknowledge options
|
|
276
|
+
* @param {number} [options.channel=0] - Channel to acknowledge alarm for (default: 0)
|
|
277
|
+
* @returns {Promise<Object>} Response from the device
|
|
278
|
+
* @throws {import('../lib/errors/errors').UnconnectedError} If device is not connected
|
|
279
|
+
* @throws {import('../lib/errors/errors').CommandTimeoutError} If command times out
|
|
280
|
+
*/
|
|
281
|
+
async acknowledgeThermostatAlarm(options = {}) {
|
|
282
|
+
const channel = normalizeChannel(options);
|
|
283
|
+
const payload = {
|
|
284
|
+
alarm: [{
|
|
285
|
+
channel,
|
|
286
|
+
temp: 0,
|
|
287
|
+
type: 0
|
|
288
|
+
}]
|
|
289
|
+
};
|
|
290
|
+
return await this.publishMessage('SETACK', 'Appliance.Control.Thermostat.Alarm', payload);
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Gets the thermostat hold action configuration from the device.
|
|
295
|
+
* @param {Object} [options={}] - Get options
|
|
296
|
+
* @param {number} [options.channel=0] - Channel to get hold action for (default: 0)
|
|
297
|
+
* @returns {Promise<Object>} Response containing hold action configuration
|
|
298
|
+
*/
|
|
299
|
+
async getThermostatHoldAction(options = {}) {
|
|
300
|
+
const channel = normalizeChannel(options);
|
|
301
|
+
const payload = {
|
|
302
|
+
holdAction: [{
|
|
303
|
+
channel
|
|
304
|
+
}]
|
|
305
|
+
};
|
|
306
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.HoldAction', payload);
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Controls (sets) the thermostat hold action configuration.
|
|
311
|
+
* @param {Object} options - Hold action options
|
|
312
|
+
* @param {Object|Array} options.holdActionData - Hold action data object (array of hold action items)
|
|
313
|
+
* @returns {Promise<Object>} Response from the device
|
|
314
|
+
*/
|
|
315
|
+
async setThermostatHoldAction(options = {}) {
|
|
316
|
+
if (!options.holdActionData) {
|
|
317
|
+
throw new Error('holdActionData is required');
|
|
318
|
+
}
|
|
319
|
+
const payload = { holdAction: Array.isArray(options.holdActionData) ? options.holdActionData : [options.holdActionData] };
|
|
320
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.HoldAction', payload);
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Gets the thermostat hold action configuration from the device.
|
|
325
|
+
* @param {Object} [options={}] - Get options
|
|
326
|
+
* @param {number} [options.channel=0] - Channel to get hold action for (default: 0)
|
|
327
|
+
* @returns {Promise<Object>} Response containing hold action configuration
|
|
328
|
+
*/
|
|
329
|
+
async getThermostatHoldAction(options = {}) {
|
|
330
|
+
const channel = normalizeChannel(options);
|
|
331
|
+
const payload = {
|
|
332
|
+
holdAction: [{
|
|
333
|
+
channel
|
|
334
|
+
}]
|
|
335
|
+
};
|
|
336
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.HoldAction', payload);
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Controls (sets) the thermostat hold action configuration.
|
|
341
|
+
* @param {Object} options - Hold action options
|
|
342
|
+
* @param {Object|Array} options.holdActionData - Hold action data object (array of hold action items)
|
|
343
|
+
* @returns {Promise<Object>} Response from the device
|
|
344
|
+
*/
|
|
345
|
+
async setThermostatHoldAction(options = {}) {
|
|
346
|
+
if (!options.holdActionData) {
|
|
347
|
+
throw new Error('holdActionData is required');
|
|
348
|
+
}
|
|
349
|
+
const payload = { holdAction: Array.isArray(options.holdActionData) ? options.holdActionData : [options.holdActionData] };
|
|
350
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.HoldAction', payload);
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Gets the thermostat overheat protection configuration from the device.
|
|
355
|
+
* @param {Object} [options={}] - Get options
|
|
356
|
+
* @param {number} [options.channel=0] - Channel to get overheat config for (default: 0)
|
|
357
|
+
* @returns {Promise<Object>} Response containing overheat configuration
|
|
358
|
+
*/
|
|
359
|
+
async getThermostatOverheat(options = {}) {
|
|
360
|
+
const channel = normalizeChannel(options);
|
|
361
|
+
const payload = {
|
|
362
|
+
overheat: [{
|
|
363
|
+
channel
|
|
364
|
+
}]
|
|
365
|
+
};
|
|
366
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.Overheat', payload);
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Controls (sets) the thermostat overheat protection configuration.
|
|
371
|
+
* @param {Object} options - Overheat options
|
|
372
|
+
* @param {Object|Array} options.overheatData - Overheat data object (array of overheat items)
|
|
373
|
+
* @returns {Promise<Object>} Response from the device
|
|
374
|
+
*/
|
|
375
|
+
async setThermostatOverheat(options = {}) {
|
|
376
|
+
if (!options.overheatData) {
|
|
377
|
+
throw new Error('overheatData is required');
|
|
378
|
+
}
|
|
379
|
+
const payload = { overheat: Array.isArray(options.overheatData) ? options.overheatData : [options.overheatData] };
|
|
380
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.Overheat', payload);
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Gets the thermostat dead zone configuration from the device.
|
|
385
|
+
* @param {Object} [options={}] - Get options
|
|
386
|
+
* @param {number} [options.channel=0] - Channel to get dead zone for (default: 0)
|
|
387
|
+
* @returns {Promise<Object>} Response containing dead zone configuration
|
|
388
|
+
*/
|
|
389
|
+
async getThermostatDeadZone(options = {}) {
|
|
390
|
+
const channel = normalizeChannel(options);
|
|
391
|
+
const payload = {
|
|
392
|
+
deadZone: [{
|
|
393
|
+
channel
|
|
394
|
+
}]
|
|
395
|
+
};
|
|
396
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.DeadZone', payload);
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Controls (sets) the thermostat dead zone configuration.
|
|
401
|
+
* @param {Object} options - Dead zone options
|
|
402
|
+
* @param {Object|Array} options.deadZoneData - Dead zone data object (array of dead zone items)
|
|
403
|
+
* @returns {Promise<Object>} Response from the device
|
|
404
|
+
*/
|
|
405
|
+
async setThermostatDeadZone(options = {}) {
|
|
406
|
+
if (!options.deadZoneData) {
|
|
407
|
+
throw new Error('deadZoneData is required');
|
|
408
|
+
}
|
|
409
|
+
const payload = { deadZone: Array.isArray(options.deadZoneData) ? options.deadZoneData : [options.deadZoneData] };
|
|
410
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.DeadZone', payload);
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Gets the thermostat calibration configuration from the device.
|
|
415
|
+
* @param {Object} [options={}] - Get options
|
|
416
|
+
* @param {number} [options.channel=0] - Channel to get calibration for (default: 0)
|
|
417
|
+
* @returns {Promise<Object>} Response containing calibration configuration
|
|
418
|
+
*/
|
|
419
|
+
async getThermostatCalibration(options = {}) {
|
|
420
|
+
const channel = normalizeChannel(options);
|
|
421
|
+
const payload = {
|
|
422
|
+
calibration: [{
|
|
423
|
+
channel
|
|
424
|
+
}]
|
|
425
|
+
};
|
|
426
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.Calibration', payload);
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Controls (sets) the thermostat calibration configuration.
|
|
431
|
+
* @param {Object} options - Calibration options
|
|
432
|
+
* @param {Object|Array} options.calibrationData - Calibration data object (array of calibration items)
|
|
433
|
+
* @returns {Promise<Object>} Response from the device
|
|
434
|
+
*/
|
|
435
|
+
async setThermostatCalibration(options = {}) {
|
|
436
|
+
if (!options.calibrationData) {
|
|
437
|
+
throw new Error('calibrationData is required');
|
|
438
|
+
}
|
|
439
|
+
const payload = { calibration: Array.isArray(options.calibrationData) ? options.calibrationData : [options.calibrationData] };
|
|
440
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.Calibration', payload);
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Gets the thermostat sensor mode configuration from the device.
|
|
445
|
+
* @param {Object} [options={}] - Get options
|
|
446
|
+
* @param {number} [options.channel=0] - Channel to get sensor mode for (default: 0)
|
|
447
|
+
* @returns {Promise<Object>} Response containing sensor mode configuration
|
|
448
|
+
*/
|
|
449
|
+
async getThermostatSensor(options = {}) {
|
|
450
|
+
const channel = normalizeChannel(options);
|
|
451
|
+
const payload = {
|
|
452
|
+
sensor: [{
|
|
453
|
+
channel
|
|
454
|
+
}]
|
|
455
|
+
};
|
|
456
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.Sensor', payload);
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Controls (sets) the thermostat sensor mode configuration.
|
|
461
|
+
* @param {Object} options - Sensor options
|
|
462
|
+
* @param {Object|Array} options.sensorData - Sensor data object (array of sensor items)
|
|
463
|
+
* @returns {Promise<Object>} Response from the device
|
|
464
|
+
*/
|
|
465
|
+
async setThermostatSensor(options = {}) {
|
|
466
|
+
if (!options.sensorData) {
|
|
467
|
+
throw new Error('sensorData is required');
|
|
468
|
+
}
|
|
469
|
+
const payload = { sensor: Array.isArray(options.sensorData) ? options.sensorData : [options.sensorData] };
|
|
470
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.Sensor', payload);
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Gets the thermostat summer mode configuration from the device.
|
|
475
|
+
* @param {Object} [options={}] - Get options
|
|
476
|
+
* @param {number} [options.channel=0] - Channel to get summer mode for (default: 0)
|
|
477
|
+
* @returns {Promise<Object>} Response containing summer mode configuration
|
|
478
|
+
*/
|
|
479
|
+
async getThermostatSummerMode(options = {}) {
|
|
480
|
+
const channel = normalizeChannel(options);
|
|
481
|
+
const payload = {
|
|
482
|
+
summerMode: [{
|
|
483
|
+
channel
|
|
484
|
+
}]
|
|
485
|
+
};
|
|
486
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.SummerMode', payload);
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Controls (sets) the thermostat summer mode configuration.
|
|
491
|
+
* @param {Object} options - Summer mode options
|
|
492
|
+
* @param {Object|Array} options.summerModeData - Summer mode data object (array of summer mode items)
|
|
493
|
+
* @returns {Promise<Object>} Response from the device
|
|
494
|
+
*/
|
|
495
|
+
async setThermostatSummerMode(options = {}) {
|
|
496
|
+
if (!options.summerModeData) {
|
|
497
|
+
throw new Error('summerModeData is required');
|
|
498
|
+
}
|
|
499
|
+
const payload = { summerMode: Array.isArray(options.summerModeData) ? options.summerModeData : [options.summerModeData] };
|
|
500
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.SummerMode', payload);
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Gets the thermostat frost protection configuration from the device.
|
|
505
|
+
* @param {Object} [options={}] - Get options
|
|
506
|
+
* @param {number} [options.channel=0] - Channel to get frost config for (default: 0)
|
|
507
|
+
* @returns {Promise<Object>} Response containing frost configuration
|
|
508
|
+
*/
|
|
509
|
+
async getThermostatFrost(options = {}) {
|
|
510
|
+
const channel = normalizeChannel(options);
|
|
511
|
+
const payload = {
|
|
512
|
+
frost: [{
|
|
513
|
+
channel
|
|
514
|
+
}]
|
|
515
|
+
};
|
|
516
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.Frost', payload);
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Controls (sets) the thermostat frost protection configuration.
|
|
521
|
+
* @param {Object} options - Frost options
|
|
522
|
+
* @param {Object|Array} options.frostData - Frost data object (array of frost items)
|
|
523
|
+
* @returns {Promise<Object>} Response from the device
|
|
524
|
+
*/
|
|
525
|
+
async setThermostatFrost(options = {}) {
|
|
526
|
+
if (!options.frostData) {
|
|
527
|
+
throw new Error('frostData is required');
|
|
528
|
+
}
|
|
529
|
+
const payload = { frost: Array.isArray(options.frostData) ? options.frostData : [options.frostData] };
|
|
530
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.Frost', payload);
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Gets the thermostat alarm configuration from the device.
|
|
535
|
+
* @param {Object} [options={}] - Get options
|
|
536
|
+
* @param {number} [options.channel=0] - Channel to get alarm config for (default: 0)
|
|
537
|
+
* @returns {Promise<Object>} Response containing alarm configuration
|
|
538
|
+
*/
|
|
539
|
+
async getThermostatAlarmConfig(options = {}) {
|
|
540
|
+
const channel = normalizeChannel(options);
|
|
541
|
+
const payload = {
|
|
542
|
+
alarmConfig: [{
|
|
543
|
+
channel
|
|
544
|
+
}]
|
|
545
|
+
};
|
|
546
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.AlarmConfig', payload);
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Controls (sets) the thermostat alarm configuration.
|
|
551
|
+
* @param {Object} options - Alarm config options
|
|
552
|
+
* @param {Object|Array} options.alarmConfigData - Alarm config data object (array of alarm config items)
|
|
553
|
+
* @returns {Promise<Object>} Response from the device
|
|
554
|
+
*/
|
|
555
|
+
async setThermostatAlarmConfig(options = {}) {
|
|
556
|
+
if (!options.alarmConfigData) {
|
|
557
|
+
throw new Error('alarmConfigData is required');
|
|
558
|
+
}
|
|
559
|
+
const payload = { alarmConfig: Array.isArray(options.alarmConfigData) ? options.alarmConfigData : [options.alarmConfigData] };
|
|
560
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.AlarmConfig', payload);
|
|
561
|
+
},
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Gets the thermostat compressor delay configuration from the device.
|
|
565
|
+
* @param {Object} [options={}] - Get options
|
|
566
|
+
* @param {number} [options.channel=0] - Channel to get compressor delay for (default: 0)
|
|
567
|
+
* @returns {Promise<Object>} Response containing compressor delay configuration
|
|
568
|
+
*/
|
|
569
|
+
async getThermostatCompressorDelay(options = {}) {
|
|
570
|
+
const channel = normalizeChannel(options);
|
|
571
|
+
const payload = {
|
|
572
|
+
delay: [{
|
|
573
|
+
channel
|
|
574
|
+
}]
|
|
575
|
+
};
|
|
576
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.CompressorDelay', payload);
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Controls (sets) the thermostat compressor delay configuration.
|
|
581
|
+
* @param {Object} options - Delay options
|
|
582
|
+
* @param {Object|Array} options.delayData - Delay data object (array of delay items)
|
|
583
|
+
* @returns {Promise<Object>} Response from the device
|
|
584
|
+
*/
|
|
585
|
+
async setThermostatCompressorDelay(options = {}) {
|
|
586
|
+
if (!options.delayData) {
|
|
587
|
+
throw new Error('delayData is required');
|
|
588
|
+
}
|
|
589
|
+
const payload = { delay: Array.isArray(options.delayData) ? options.delayData : [options.delayData] };
|
|
590
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.CompressorDelay', payload);
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Gets the thermostat control range configuration from the device.
|
|
595
|
+
* @param {Object} [options={}] - Get options
|
|
596
|
+
* @param {number} [options.channel=0] - Channel to get control range for (default: 0)
|
|
597
|
+
* @returns {Promise<Object>} Response containing control range configuration
|
|
598
|
+
*/
|
|
599
|
+
async getThermostatCtlRange(options = {}) {
|
|
600
|
+
const channel = normalizeChannel(options);
|
|
601
|
+
const payload = {
|
|
602
|
+
ctlRange: [{
|
|
603
|
+
channel
|
|
604
|
+
}]
|
|
605
|
+
};
|
|
606
|
+
return await this.publishMessage('GET', 'Appliance.Control.Thermostat.CtlRange', payload);
|
|
607
|
+
},
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Controls (sets) the thermostat control range configuration.
|
|
611
|
+
* @param {Object} options - Control range options
|
|
612
|
+
* @param {Object|Array} options.ctlRangeData - Control range data object (array of ctlRange items)
|
|
613
|
+
* @returns {Promise<Object>} Response from the device
|
|
614
|
+
*/
|
|
615
|
+
async setThermostatCtlRange(options = {}) {
|
|
616
|
+
if (!options.ctlRangeData) {
|
|
617
|
+
throw new Error('ctlRangeData is required');
|
|
618
|
+
}
|
|
619
|
+
const payload = { ctlRange: Array.isArray(options.ctlRangeData) ? options.ctlRangeData : [options.ctlRangeData] };
|
|
620
|
+
return await this.publishMessage('SET', 'Appliance.Control.Thermostat.CtlRange', payload);
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Gets the cached thermostat state for the specified channel.
|
|
625
|
+
*
|
|
626
|
+
* Returns cached state without making a request. Use {@link getThermostatMode} to fetch fresh state
|
|
627
|
+
* from the device. The state object contains enum properties: mode (ThermostatMode), workingMode (ThermostatWorkingMode).
|
|
628
|
+
*
|
|
629
|
+
* @param {number} [channel=0] - Channel to get state for (default: 0)
|
|
630
|
+
* @returns {import('../lib/model/states/thermostat-state').ThermostatState|undefined} Cached thermostat state or undefined if not available
|
|
631
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
632
|
+
*/
|
|
633
|
+
getCachedThermostatState(channel = 0) {
|
|
634
|
+
this.validateState();
|
|
635
|
+
return this._thermostatStateByChannel.get(channel);
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Gets the cached thermostat mode B state for the specified channel.
|
|
640
|
+
*
|
|
641
|
+
* Returns cached state without making a request. Use {@link getThermostatModeB} to fetch fresh state
|
|
642
|
+
* from the device. The state object contains enum properties: workingMode (ThermostatWorkingMode), state (ThermostatModeBState).
|
|
643
|
+
*
|
|
644
|
+
* @param {number} [channel=0] - Channel to get state for (default: 0)
|
|
645
|
+
* @returns {import('../lib/model/states/thermostat-state').ThermostatState|undefined} Cached thermostat mode B state or undefined if not available
|
|
646
|
+
* @throws {Error} If state has not been initialized (call refreshState() first)
|
|
647
|
+
*/
|
|
648
|
+
getCachedThermostatModeBState(channel = 0) {
|
|
649
|
+
this.validateState();
|
|
650
|
+
return this._thermostatStateByChannel.get(channel);
|
|
651
|
+
},
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Aligns temperature value to device units and validates range.
|
|
655
|
+
*
|
|
656
|
+
* Converts Celsius temperature to device units (tenths of degrees) and rounds to nearest 0.5°C increment.
|
|
657
|
+
* Validates against device-specific min/max temperature limits if available in cached state.
|
|
658
|
+
*
|
|
659
|
+
* @param {number} temperature - Temperature in Celsius
|
|
660
|
+
* @param {number} [channel=0] - Channel to get temperature limits for (default: 0)
|
|
661
|
+
* @returns {number} Temperature in device units (tenths of degrees)
|
|
662
|
+
* @throws {import('../lib/errors/errors').CommandError} If temperature is out of valid range
|
|
663
|
+
* @private
|
|
664
|
+
*/
|
|
665
|
+
_alignThermostatTemperature(temperature, channel = 0) {
|
|
666
|
+
const THERMOSTAT_MIN_SETTABLE_TEMP = 5.0;
|
|
667
|
+
const THERMOSTAT_MAX_SETTABLE_TEMP = 35.0;
|
|
668
|
+
|
|
669
|
+
let minSetableTemp = THERMOSTAT_MIN_SETTABLE_TEMP;
|
|
670
|
+
let maxSetableTemp = THERMOSTAT_MAX_SETTABLE_TEMP;
|
|
671
|
+
|
|
672
|
+
const channelState = this._thermostatStateByChannel.get(channel);
|
|
673
|
+
if (channelState) {
|
|
674
|
+
if (channelState.minTemperatureCelsius !== undefined) {
|
|
675
|
+
minSetableTemp = channelState.minTemperatureCelsius;
|
|
676
|
+
}
|
|
677
|
+
if (channelState.maxTemperatureCelsius !== undefined) {
|
|
678
|
+
maxSetableTemp = channelState.maxTemperatureCelsius;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (temperature < minSetableTemp || temperature > maxSetableTemp) {
|
|
683
|
+
const { CommandError } = require('../../model/exception');
|
|
684
|
+
throw new CommandError(
|
|
685
|
+
`Temperature ${temperature}°C is out of range (${minSetableTemp}-${maxSetableTemp}°C) for this device`,
|
|
686
|
+
{ temperature, minSetableTemp, maxSetableTemp },
|
|
687
|
+
this.uuid
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const quotient = temperature / 0.5;
|
|
692
|
+
const rounded = Math.round(quotient);
|
|
693
|
+
const finalTemp = rounded * 0.5;
|
|
694
|
+
|
|
695
|
+
return Math.round(finalTemp * 10);
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Updates the cached thermostat mode state from mode data.
|
|
700
|
+
*
|
|
701
|
+
* Called automatically when thermostat mode push notifications are received or commands complete.
|
|
702
|
+
* Handles arrays of mode data for multiple channels.
|
|
703
|
+
*
|
|
704
|
+
* @param {Array} modeData - Array of mode data objects
|
|
705
|
+
* @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
|
|
706
|
+
* @private
|
|
707
|
+
*/
|
|
708
|
+
_updateThermostatMode(modeData, source = 'response') {
|
|
709
|
+
if (!modeData || !Array.isArray(modeData)) {return;}
|
|
710
|
+
|
|
711
|
+
for (const channelData of modeData) {
|
|
712
|
+
const channelIndex = channelData.channel;
|
|
713
|
+
if (channelIndex === undefined || channelIndex === null) {continue;}
|
|
714
|
+
|
|
715
|
+
// Get old state before updating
|
|
716
|
+
const oldState = this._thermostatStateByChannel.get(channelIndex);
|
|
717
|
+
const oldValue = oldState ? {
|
|
718
|
+
mode: oldState.mode,
|
|
719
|
+
targetTemp: oldState.targetTemperatureCelsius,
|
|
720
|
+
currentTemp: oldState.currentTemperatureCelsius
|
|
721
|
+
} : undefined;
|
|
722
|
+
|
|
723
|
+
let state = this._thermostatStateByChannel.get(channelIndex);
|
|
724
|
+
if (!state) {
|
|
725
|
+
state = new ThermostatState(channelData);
|
|
726
|
+
this._thermostatStateByChannel.set(channelIndex, state);
|
|
727
|
+
} else {
|
|
728
|
+
state.update(channelData);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const newValue = buildStateChanges(oldValue, {
|
|
732
|
+
mode: state.mode,
|
|
733
|
+
targetTemp: state.targetTemperatureCelsius,
|
|
734
|
+
currentTemp: state.currentTemperatureCelsius
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
if (Object.keys(newValue).length > 0) {
|
|
738
|
+
this.emit('stateChange', {
|
|
739
|
+
type: 'thermostat',
|
|
740
|
+
channel: channelIndex,
|
|
741
|
+
value: newValue,
|
|
742
|
+
oldValue,
|
|
743
|
+
source,
|
|
744
|
+
timestamp: Date.now()
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Updates the cached thermostat mode B state from mode B data.
|
|
752
|
+
*
|
|
753
|
+
* Called automatically when thermostat mode B push notifications are received or commands complete.
|
|
754
|
+
* Handles arrays of mode B data for multiple channels.
|
|
755
|
+
*
|
|
756
|
+
* @param {Array} modeData - Array of mode B data objects
|
|
757
|
+
* @param {string} [source='response'] - Source of the update ('push' | 'poll' | 'response')
|
|
758
|
+
* @private
|
|
759
|
+
*/
|
|
760
|
+
_updateThermostatModeB(modeData, source = 'response') {
|
|
761
|
+
if (!modeData || !Array.isArray(modeData)) {return;}
|
|
762
|
+
|
|
763
|
+
for (const channelData of modeData) {
|
|
764
|
+
const channelIndex = channelData.channel;
|
|
765
|
+
if (channelIndex === undefined || channelIndex === null) {continue;}
|
|
766
|
+
|
|
767
|
+
// Get old state before updating
|
|
768
|
+
const oldState = this._thermostatStateByChannel.get(channelIndex);
|
|
769
|
+
const oldValue = oldState ? {
|
|
770
|
+
mode: oldState.mode,
|
|
771
|
+
state: oldState.state,
|
|
772
|
+
targetTemp: oldState.targetTemperatureCelsius,
|
|
773
|
+
currentTemp: oldState.currentTemperatureCelsius
|
|
774
|
+
} : undefined;
|
|
775
|
+
|
|
776
|
+
let state = this._thermostatStateByChannel.get(channelIndex);
|
|
777
|
+
if (!state) {
|
|
778
|
+
state = new ThermostatState(channelData);
|
|
779
|
+
this._thermostatStateByChannel.set(channelIndex, state);
|
|
780
|
+
} else {
|
|
781
|
+
state.update(channelData);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const newValue = buildStateChanges(oldValue, {
|
|
785
|
+
mode: state.mode,
|
|
786
|
+
state: state.state,
|
|
787
|
+
targetTemp: state.targetTemperatureCelsius,
|
|
788
|
+
currentTemp: state.currentTemperatureCelsius
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
if (Object.keys(newValue).length > 0) {
|
|
792
|
+
this.emit('stateChange', {
|
|
793
|
+
type: 'thermostat',
|
|
794
|
+
channel: channelIndex,
|
|
795
|
+
value: newValue,
|
|
796
|
+
oldValue,
|
|
797
|
+
source,
|
|
798
|
+
timestamp: Date.now()
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|