tsvesync 1.0.64 → 1.0.65
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/dist/lib/fans/airBaseV2.d.ts +68 -0
- package/dist/lib/fans/airBaseV2.js +226 -0
- package/dist/lib/fans/airBypass.d.ts +79 -0
- package/dist/lib/fans/airBypass.js +484 -0
- package/dist/lib/fans/humid200300S.d.ts +50 -0
- package/dist/lib/fans/humid200300S.js +200 -0
- package/dist/lib/fans/humidifier.d.ts +83 -0
- package/dist/lib/fans/humidifier.js +388 -0
- package/dist/lib/fans/index.d.ts +9 -0
- package/dist/lib/fans/index.js +95 -0
- package/dist/lib/fans/towerFan.d.ts +105 -0
- package/dist/lib/fans/towerFan.js +279 -0
- package/dist/lib/fans/warmHumidifier.d.ts +49 -0
- package/dist/lib/fans/warmHumidifier.js +174 -0
- package/dist/lib/vesyncFan.d.ts +2 -2
- package/dist/lib/vesyncFan.js +2 -2
- package/dist/lib/vesyncFanImpl.d.ts +1 -436
- package/dist/lib/vesyncFanImpl.js +16 -1848
- package/package.json +1 -1
- package/src/lib/fans/airBaseV2.ts +276 -0
- package/src/lib/fans/airBypass.ts +594 -0
- package/src/lib/fans/humid200300S.ts +234 -0
- package/src/lib/fans/humidifier.ts +470 -0
- package/src/lib/fans/index.ts +101 -0
- package/src/lib/fans/towerFan.ts +335 -0
- package/src/lib/fans/warmHumidifier.ts +202 -0
- package/src/lib/vesyncFan.ts +5 -5
- package/src/lib/vesyncFanImpl.ts +2 -2262
package/src/lib/vesyncFanImpl.ts
CHANGED
|
@@ -2,2265 +2,5 @@
|
|
|
2
2
|
* VeSync Fan Implementations
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { Helpers } from './helpers';
|
|
8
|
-
import { logger } from './logger';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* VeSync Air Purifier with Bypass
|
|
12
|
-
*/
|
|
13
|
-
export class VeSyncAirBypass extends VeSyncFan {
|
|
14
|
-
protected readonly modes = ['auto', 'manual', 'sleep'] as const;
|
|
15
|
-
protected readonly displayModes = ['on', 'off'] as const;
|
|
16
|
-
protected readonly childLockModes = ['on', 'off'] as const;
|
|
17
|
-
|
|
18
|
-
constructor(details: Record<string, any>, manager: VeSync) {
|
|
19
|
-
super(details, manager);
|
|
20
|
-
logger.debug(`Initialized VeSyncAirBypass device: ${this.deviceName}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Build API dictionary
|
|
25
|
-
*/
|
|
26
|
-
protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
|
|
27
|
-
const head = Helpers.reqHeaderBypass();
|
|
28
|
-
const body = {
|
|
29
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
30
|
-
cid: this.cid,
|
|
31
|
-
configModule: this.configModule,
|
|
32
|
-
payload: {
|
|
33
|
-
method,
|
|
34
|
-
source: 'APP',
|
|
35
|
-
data: {}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
return [head, body];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get device details
|
|
43
|
-
*/
|
|
44
|
-
async getDetails(): Promise<Boolean> {
|
|
45
|
-
logger.debug(`Getting details for device: ${this.deviceName}`);
|
|
46
|
-
|
|
47
|
-
// Special handling for LV series
|
|
48
|
-
if (this.deviceType.startsWith('LV-')) {
|
|
49
|
-
const [response, status] = await this.callApi(
|
|
50
|
-
'/131airPurifier/v1/device/devicedetail',
|
|
51
|
-
'post',
|
|
52
|
-
{
|
|
53
|
-
acceptLanguage: 'en',
|
|
54
|
-
accountID: this.manager.accountId!,
|
|
55
|
-
appVersion: '2.8.6',
|
|
56
|
-
method: 'devicedetail',
|
|
57
|
-
mobileId: '1234567890123456',
|
|
58
|
-
phoneBrand: 'SM N9005',
|
|
59
|
-
phoneOS: 'Android',
|
|
60
|
-
timeZone: this.manager.timeZone!,
|
|
61
|
-
token: this.manager.token!,
|
|
62
|
-
uuid: this.uuid
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
'accept-language': 'en',
|
|
66
|
-
'accountId': this.manager.accountId!,
|
|
67
|
-
'appVersion': '2.8.6',
|
|
68
|
-
'content-type': 'application/json',
|
|
69
|
-
'tk': this.manager.token!,
|
|
70
|
-
'tz': this.manager.timeZone!
|
|
71
|
-
}
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
const success = this.checkResponse([response, status], 'getDetails');
|
|
75
|
-
if (success && response) {
|
|
76
|
-
this.deviceStatus = response.result?.result?.deviceStatus === 'on' ? 'on' : 'off';
|
|
77
|
-
this.details = {
|
|
78
|
-
mode: response.result?.result?.mode || '',
|
|
79
|
-
speed: response.result?.result?.level || 0,
|
|
80
|
-
filterLife: response.result?.result?.filterLife || 0,
|
|
81
|
-
screenStatus: response.result?.result?.display || false,
|
|
82
|
-
childLock: response.result?.result?.childLock || false,
|
|
83
|
-
airQuality: response.result?.result?.airQuality || 0
|
|
84
|
-
};
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Original implementation for other devices
|
|
91
|
-
const [response, status] = await this.callApi(
|
|
92
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
93
|
-
'post',
|
|
94
|
-
{
|
|
95
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
96
|
-
cid: this.cid,
|
|
97
|
-
configModule: this.configModule,
|
|
98
|
-
payload: {
|
|
99
|
-
data: {},
|
|
100
|
-
method: 'getPurifierStatus',
|
|
101
|
-
source: 'APP'
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
Helpers.reqHeaderBypass()
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const success = this.checkResponse([response, status], 'getDetails');
|
|
108
|
-
if (success) {
|
|
109
|
-
const result = response?.result?.result;
|
|
110
|
-
if (result) {
|
|
111
|
-
this.deviceStatus = result.enabled ? 'on' : 'off';
|
|
112
|
-
this.details = {
|
|
113
|
-
mode: result.mode || '',
|
|
114
|
-
speed: result.level || 0,
|
|
115
|
-
filterLife: result.filter_life || 0,
|
|
116
|
-
screenStatus: result.display || false,
|
|
117
|
-
childLock: result.child_lock || false,
|
|
118
|
-
airQuality: result.air_quality || 0
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
if (result.configuration) {
|
|
122
|
-
this.config = result.configuration;
|
|
123
|
-
}
|
|
124
|
-
logger.debug(`Successfully got details for device: ${this.deviceName}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return success;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Turn device on
|
|
132
|
-
*/
|
|
133
|
-
async turnOn(): Promise<boolean> {
|
|
134
|
-
logger.info(`Turning on device: ${this.deviceName}`);
|
|
135
|
-
|
|
136
|
-
// Special handling for LV series
|
|
137
|
-
if (this.deviceType.startsWith('LV-')) {
|
|
138
|
-
const [response, status] = await this.callApi(
|
|
139
|
-
'/131airPurifier/v1/device/deviceStatus',
|
|
140
|
-
'put',
|
|
141
|
-
{
|
|
142
|
-
acceptLanguage: 'en',
|
|
143
|
-
accountID: this.manager.accountId!,
|
|
144
|
-
status: 'on',
|
|
145
|
-
timeZone: this.manager.timeZone!,
|
|
146
|
-
token: this.manager.token!,
|
|
147
|
-
uuid: this.uuid
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
'accept-language': 'en',
|
|
151
|
-
'accountId': this.manager.accountId!,
|
|
152
|
-
'appVersion': '2.8.6',
|
|
153
|
-
'content-type': 'application/json',
|
|
154
|
-
'tk': this.manager.token!,
|
|
155
|
-
'tz': this.manager.timeZone!
|
|
156
|
-
}
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
const success = this.checkResponse([response, status], 'turnOn');
|
|
160
|
-
if (!success) {
|
|
161
|
-
logger.error(`Failed to turn on device: ${this.deviceName}`);
|
|
162
|
-
}
|
|
163
|
-
return success;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Original implementation for other devices
|
|
167
|
-
const [response, status] = await this.callApi(
|
|
168
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
169
|
-
'post',
|
|
170
|
-
{
|
|
171
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
172
|
-
cid: this.cid,
|
|
173
|
-
configModule: this.configModule,
|
|
174
|
-
payload: {
|
|
175
|
-
data: {
|
|
176
|
-
enabled: true,
|
|
177
|
-
id: 0
|
|
178
|
-
},
|
|
179
|
-
method: 'setSwitch',
|
|
180
|
-
source: 'APP'
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
Helpers.reqHeaderBypass()
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
const success = this.checkResponse([response, status], 'turnOn');
|
|
187
|
-
if (!success) {
|
|
188
|
-
logger.error(`Failed to turn on device: ${this.deviceName}`);
|
|
189
|
-
}
|
|
190
|
-
return success;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Turn device off
|
|
195
|
-
*/
|
|
196
|
-
async turnOff(): Promise<boolean> {
|
|
197
|
-
logger.info(`Turning off device: ${this.deviceName}`);
|
|
198
|
-
|
|
199
|
-
// Special handling for LV series
|
|
200
|
-
if (this.deviceType.startsWith('LV-')) {
|
|
201
|
-
const [response, status] = await this.callApi(
|
|
202
|
-
'/131airPurifier/v1/device/deviceStatus',
|
|
203
|
-
'put',
|
|
204
|
-
{
|
|
205
|
-
acceptLanguage: 'en',
|
|
206
|
-
accountID: this.manager.accountId!,
|
|
207
|
-
status: 'off',
|
|
208
|
-
timeZone: this.manager.timeZone!,
|
|
209
|
-
token: this.manager.token!,
|
|
210
|
-
uuid: this.uuid
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
'accept-language': 'en',
|
|
214
|
-
'accountId': this.manager.accountId!,
|
|
215
|
-
'appVersion': '2.8.6',
|
|
216
|
-
'content-type': 'application/json',
|
|
217
|
-
'tk': this.manager.token!,
|
|
218
|
-
'tz': this.manager.timeZone!
|
|
219
|
-
}
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const success = this.checkResponse([response, status], 'turnOff');
|
|
223
|
-
if (!success) {
|
|
224
|
-
logger.error(`Failed to turn off device: ${this.deviceName}`);
|
|
225
|
-
}
|
|
226
|
-
return success;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Original implementation for other devices
|
|
230
|
-
const [response, status] = await this.callApi(
|
|
231
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
232
|
-
'post',
|
|
233
|
-
{
|
|
234
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
235
|
-
cid: this.cid,
|
|
236
|
-
configModule: this.configModule,
|
|
237
|
-
payload: {
|
|
238
|
-
data: {
|
|
239
|
-
enabled: false,
|
|
240
|
-
id: 0
|
|
241
|
-
},
|
|
242
|
-
method: 'setSwitch',
|
|
243
|
-
source: 'APP'
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
Helpers.reqHeaderBypass()
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
const success = this.checkResponse([response, status], 'turnOff');
|
|
250
|
-
if (!success) {
|
|
251
|
-
logger.error(`Failed to turn off device: ${this.deviceName}`);
|
|
252
|
-
}
|
|
253
|
-
return success;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Change fan speed
|
|
258
|
-
*/
|
|
259
|
-
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
260
|
-
// const speeds = this.config.levels ?? [];
|
|
261
|
-
// if (!speeds.includes(speed)) {
|
|
262
|
-
// logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
|
|
263
|
-
// return false;
|
|
264
|
-
// }
|
|
265
|
-
|
|
266
|
-
logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
|
|
267
|
-
const [response, status] = await this.callApi(
|
|
268
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
269
|
-
'post',
|
|
270
|
-
{
|
|
271
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
272
|
-
cid: this.cid,
|
|
273
|
-
configModule: this.configModule,
|
|
274
|
-
payload: {
|
|
275
|
-
data: {
|
|
276
|
-
id: 0,
|
|
277
|
-
level: speed,
|
|
278
|
-
mode: 'manual',
|
|
279
|
-
type: 'wind'
|
|
280
|
-
},
|
|
281
|
-
method: 'setLevel',
|
|
282
|
-
source: 'APP'
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
Helpers.reqHeaderBypass()
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
const success = this.checkResponse([response, status], 'changeFanSpeed');
|
|
289
|
-
if (!success) {
|
|
290
|
-
logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
|
|
291
|
-
}
|
|
292
|
-
return success;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Set device mode
|
|
297
|
-
*/
|
|
298
|
-
async setMode(mode: string): Promise<boolean> {
|
|
299
|
-
if (!this.modes.includes(mode as any)) {
|
|
300
|
-
const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
|
|
301
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
302
|
-
throw new Error(error);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
|
|
306
|
-
const [response, status] = await this.callApi(
|
|
307
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
308
|
-
'post',
|
|
309
|
-
{
|
|
310
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
311
|
-
cid: this.cid,
|
|
312
|
-
configModule: this.configModule,
|
|
313
|
-
payload: {
|
|
314
|
-
data: {
|
|
315
|
-
mode
|
|
316
|
-
},
|
|
317
|
-
method: 'setPurifierMode',
|
|
318
|
-
source: 'APP'
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
Helpers.reqHeaderBypass()
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
const success = this.checkResponse([response, status], 'setMode');
|
|
325
|
-
if (!success) {
|
|
326
|
-
logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
|
|
327
|
-
}
|
|
328
|
-
return success;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Set display status
|
|
333
|
-
*/
|
|
334
|
-
async setDisplay(enabled: boolean): Promise<boolean> {
|
|
335
|
-
if (!this.hasFeature('display')) {
|
|
336
|
-
const error = 'Display control not supported';
|
|
337
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
338
|
-
throw new Error(error);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
logger.debug(`Setting display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
342
|
-
const [response, status] = await this.callApi(
|
|
343
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
344
|
-
'post',
|
|
345
|
-
{
|
|
346
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
347
|
-
cid: this.cid,
|
|
348
|
-
configModule: this.configModule,
|
|
349
|
-
payload: {
|
|
350
|
-
data: {
|
|
351
|
-
state: enabled
|
|
352
|
-
},
|
|
353
|
-
method: 'setDisplay',
|
|
354
|
-
source: 'APP'
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
Helpers.reqHeaderBypass()
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
const success = this.checkResponse([response, status], 'setDisplay');
|
|
361
|
-
if (!success) {
|
|
362
|
-
logger.error(`Failed to set display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
363
|
-
}
|
|
364
|
-
return success;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Set child lock
|
|
369
|
-
*/
|
|
370
|
-
async setChildLock(enabled: boolean): Promise<boolean> {
|
|
371
|
-
if (!this.hasFeature('child_lock')) {
|
|
372
|
-
const error = 'Child lock not supported';
|
|
373
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
374
|
-
throw new Error(error);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
logger.debug(`Setting child lock to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
378
|
-
const [response, status] = await this.callApi(
|
|
379
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
380
|
-
'post',
|
|
381
|
-
{
|
|
382
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
383
|
-
cid: this.cid,
|
|
384
|
-
configModule: this.configModule,
|
|
385
|
-
payload: {
|
|
386
|
-
data: {
|
|
387
|
-
state: enabled
|
|
388
|
-
},
|
|
389
|
-
method: 'setChildLock',
|
|
390
|
-
source: 'APP'
|
|
391
|
-
}
|
|
392
|
-
},
|
|
393
|
-
Helpers.reqHeaderBypass()
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
const success = this.checkResponse([response, status], 'setChildLock');
|
|
397
|
-
if (!success) {
|
|
398
|
-
logger.error(`Failed to set child lock to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
399
|
-
}
|
|
400
|
-
return success;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Set timer
|
|
405
|
-
*/
|
|
406
|
-
async setTimer(hours: number): Promise<boolean> {
|
|
407
|
-
if (!this.hasFeature('timer')) {
|
|
408
|
-
const error = 'Timer not supported';
|
|
409
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
410
|
-
throw new Error(error);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
logger.debug(`Setting timer to ${hours} hours for device: ${this.deviceName}`);
|
|
414
|
-
const [response, status] = await this.callApi(
|
|
415
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
416
|
-
'post',
|
|
417
|
-
{
|
|
418
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
419
|
-
cid: this.cid,
|
|
420
|
-
configModule: this.configModule,
|
|
421
|
-
payload: {
|
|
422
|
-
data: {
|
|
423
|
-
action: 'off',
|
|
424
|
-
total: hours * 3600
|
|
425
|
-
},
|
|
426
|
-
method: 'addTimer',
|
|
427
|
-
source: 'APP'
|
|
428
|
-
}
|
|
429
|
-
},
|
|
430
|
-
Helpers.reqHeaderBypass()
|
|
431
|
-
);
|
|
432
|
-
|
|
433
|
-
const success = this.checkResponse([response, status], 'setTimer');
|
|
434
|
-
if (!success) {
|
|
435
|
-
logger.error(`Failed to set timer to ${hours} hours for device: ${this.deviceName}`);
|
|
436
|
-
}
|
|
437
|
-
return success;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Clear timer
|
|
442
|
-
*/
|
|
443
|
-
async clearTimer(): Promise<boolean> {
|
|
444
|
-
if (!this.hasFeature('timer')) {
|
|
445
|
-
const error = 'Timer not supported';
|
|
446
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
447
|
-
throw new Error(error);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
logger.debug(`Clearing timer for device: ${this.deviceName}`);
|
|
451
|
-
const [response, status] = await this.callApi(
|
|
452
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
453
|
-
'post',
|
|
454
|
-
{
|
|
455
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
456
|
-
cid: this.cid,
|
|
457
|
-
configModule: this.configModule,
|
|
458
|
-
payload: {
|
|
459
|
-
data: {},
|
|
460
|
-
method: 'deleteTimer',
|
|
461
|
-
source: 'APP'
|
|
462
|
-
}
|
|
463
|
-
},
|
|
464
|
-
Helpers.reqHeaderBypass()
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
const success = this.checkResponse([response, status], 'clearTimer');
|
|
468
|
-
if (!success) {
|
|
469
|
-
logger.error(`Failed to clear timer for device: ${this.deviceName}`);
|
|
470
|
-
}
|
|
471
|
-
return success;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Turn automatic stop on
|
|
476
|
-
*/
|
|
477
|
-
async automaticStopOn(): Promise<boolean> {
|
|
478
|
-
logger.debug(`Setting automatic stop on for device: ${this.deviceName}`);
|
|
479
|
-
const [response, status] = await this.callApi(
|
|
480
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
481
|
-
'post',
|
|
482
|
-
{
|
|
483
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
484
|
-
cid: this.cid,
|
|
485
|
-
configModule: this.configModule,
|
|
486
|
-
payload: {
|
|
487
|
-
data: {
|
|
488
|
-
enabled: true
|
|
489
|
-
},
|
|
490
|
-
method: 'setAutomaticStop',
|
|
491
|
-
source: 'APP'
|
|
492
|
-
}
|
|
493
|
-
},
|
|
494
|
-
Helpers.reqHeaderBypass()
|
|
495
|
-
);
|
|
496
|
-
|
|
497
|
-
const success = this.checkResponse([response, status], 'automaticStopOn');
|
|
498
|
-
if (!success) {
|
|
499
|
-
logger.error(`Failed to set automatic stop on for device: ${this.deviceName}`);
|
|
500
|
-
}
|
|
501
|
-
return success;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Turn automatic stop off
|
|
506
|
-
*/
|
|
507
|
-
async automaticStopOff(): Promise<boolean> {
|
|
508
|
-
logger.debug(`Setting automatic stop off for device: ${this.deviceName}`);
|
|
509
|
-
const [response, status] = await this.callApi(
|
|
510
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
511
|
-
'post',
|
|
512
|
-
{
|
|
513
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
514
|
-
cid: this.cid,
|
|
515
|
-
configModule: this.configModule,
|
|
516
|
-
payload: {
|
|
517
|
-
data: {
|
|
518
|
-
enabled: false
|
|
519
|
-
},
|
|
520
|
-
method: 'setAutomaticStop',
|
|
521
|
-
source: 'APP'
|
|
522
|
-
}
|
|
523
|
-
},
|
|
524
|
-
Helpers.reqHeaderBypass()
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
const success = this.checkResponse([response, status], 'automaticStopOff');
|
|
528
|
-
if (!success) {
|
|
529
|
-
logger.error(`Failed to set automatic stop off for device: ${this.deviceName}`);
|
|
530
|
-
}
|
|
531
|
-
return success;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Set auto mode
|
|
536
|
-
*/
|
|
537
|
-
async autoMode(): Promise<boolean> {
|
|
538
|
-
logger.debug(`Setting auto mode for device: ${this.deviceName}`);
|
|
539
|
-
const success = await this.setMode('auto');
|
|
540
|
-
if (!success) {
|
|
541
|
-
logger.error(`Failed to set auto mode for device: ${this.deviceName}`);
|
|
542
|
-
}
|
|
543
|
-
return success;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Set manual mode
|
|
548
|
-
*/
|
|
549
|
-
async manualMode(): Promise<boolean> {
|
|
550
|
-
logger.debug(`Setting manual mode for device: ${this.deviceName}`);
|
|
551
|
-
const success = await this.setMode('manual');
|
|
552
|
-
if (!success) {
|
|
553
|
-
logger.error(`Failed to set manual mode for device: ${this.deviceName}`);
|
|
554
|
-
}
|
|
555
|
-
return success;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Set sleep mode
|
|
560
|
-
*/
|
|
561
|
-
async sleepMode(): Promise<boolean> {
|
|
562
|
-
logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
|
|
563
|
-
const success = await this.setMode('sleep');
|
|
564
|
-
if (!success) {
|
|
565
|
-
logger.error(`Failed to set sleep mode for device: ${this.deviceName}`);
|
|
566
|
-
}
|
|
567
|
-
return success;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Turn off display
|
|
572
|
-
*/
|
|
573
|
-
async turnOffDisplay(): Promise<boolean> {
|
|
574
|
-
logger.debug(`Turning off display for device: ${this.deviceName}`);
|
|
575
|
-
const success = await this.setDisplay(false);
|
|
576
|
-
if (!success) {
|
|
577
|
-
logger.error(`Failed to turn off display for device: ${this.deviceName}`);
|
|
578
|
-
}
|
|
579
|
-
return success;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* Turn on display
|
|
584
|
-
*/
|
|
585
|
-
async turnOnDisplay(): Promise<boolean> {
|
|
586
|
-
logger.debug(`Turning on display for device: ${this.deviceName}`);
|
|
587
|
-
const success = await this.setDisplay(true);
|
|
588
|
-
if (!success) {
|
|
589
|
-
logger.error(`Failed to turn on display for device: ${this.deviceName}`);
|
|
590
|
-
}
|
|
591
|
-
return success;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* VeSync Humidifier Base Class
|
|
597
|
-
*/
|
|
598
|
-
export class VeSyncHumidifier extends VeSyncFan {
|
|
599
|
-
protected readonly modes = ['auto', 'manual', 'sleep'] as const;
|
|
600
|
-
protected readonly mistLevels: number[];
|
|
601
|
-
protected readonly displayModes = ['on', 'off'] as const;
|
|
602
|
-
protected readonly humidityRange = { min: 30, max: 80 };
|
|
603
|
-
|
|
604
|
-
constructor(details: Record<string, any>, manager: VeSync) {
|
|
605
|
-
super(details, manager);
|
|
606
|
-
// Set mist levels based on model
|
|
607
|
-
switch (this.deviceType) {
|
|
608
|
-
case 'Dual200S':
|
|
609
|
-
case 'LUH-D301S-WUSR':
|
|
610
|
-
case 'LUH-D301S-WJP':
|
|
611
|
-
case 'LUH-D301S-WEU':
|
|
612
|
-
this.mistLevels = [1, 2];
|
|
613
|
-
break;
|
|
614
|
-
default:
|
|
615
|
-
// All other models support levels 1-9
|
|
616
|
-
this.mistLevels = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
617
|
-
}
|
|
618
|
-
logger.debug(`Initialized VeSyncHumidifier device: ${this.deviceName}`);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* Get device details
|
|
623
|
-
*/
|
|
624
|
-
async getDetails(): Promise<Boolean> {
|
|
625
|
-
logger.debug(`Getting details for device: ${this.deviceName}`);
|
|
626
|
-
const [response, status] = await this.callApi(
|
|
627
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
628
|
-
'post',
|
|
629
|
-
{
|
|
630
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
631
|
-
cid: this.cid,
|
|
632
|
-
configModule: this.configModule,
|
|
633
|
-
payload: {
|
|
634
|
-
data: {},
|
|
635
|
-
method: 'getHumidifierStatus',
|
|
636
|
-
source: 'APP'
|
|
637
|
-
}
|
|
638
|
-
},
|
|
639
|
-
Helpers.reqHeaderBypass()
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
const success = this.checkResponse([response, status], 'getDetails');
|
|
643
|
-
if (success && response?.result?.result) {
|
|
644
|
-
const result = response.result.result;
|
|
645
|
-
|
|
646
|
-
// Handle model-specific status fields
|
|
647
|
-
if (this.deviceType.startsWith('Classic') || this.deviceType === 'Dual200S') {
|
|
648
|
-
this.deviceStatus = result.enabled ? 'on' : 'off';
|
|
649
|
-
} else {
|
|
650
|
-
this.deviceStatus = result.powerSwitch === 1 ? 'on' : 'off';
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
this.details = {
|
|
654
|
-
mode: result.mode || '',
|
|
655
|
-
humidity: result.humidity || 0,
|
|
656
|
-
mist_level: result.mistLevel || 0,
|
|
657
|
-
mist_virtual_level: result.virtualLevel || 0,
|
|
658
|
-
warm_level: result.warmLevel || 0,
|
|
659
|
-
water_lacks: result.waterLacks || false,
|
|
660
|
-
humidity_high: result.humidityHigh || false,
|
|
661
|
-
water_tank_lifted: result.waterTankLifted || false,
|
|
662
|
-
display: result.display || false,
|
|
663
|
-
automatic_stop: result.automaticStop || false,
|
|
664
|
-
configuration: result.configuration || {},
|
|
665
|
-
connection_status: result.connectionStatus || null
|
|
666
|
-
};
|
|
667
|
-
logger.debug(`Successfully got details for device: ${this.deviceName}`);
|
|
668
|
-
}
|
|
669
|
-
return success;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* Turn device on
|
|
674
|
-
*/
|
|
675
|
-
async turnOn(): Promise<boolean> {
|
|
676
|
-
return this.toggleSwitch(true);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Turn device off
|
|
681
|
-
*/
|
|
682
|
-
async turnOff(): Promise<boolean> {
|
|
683
|
-
return this.toggleSwitch(false);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Get model-specific toggle payload
|
|
688
|
-
*/
|
|
689
|
-
protected getTogglePayload(enabled: boolean): Record<string, any> {
|
|
690
|
-
// Model-specific toggle payload based on PyVeSync implementation
|
|
691
|
-
if (this.deviceType.startsWith('Classic') || this.deviceType === 'Dual200S') {
|
|
692
|
-
return {
|
|
693
|
-
enabled: enabled,
|
|
694
|
-
id: 0
|
|
695
|
-
};
|
|
696
|
-
} else if (this.deviceType.startsWith('Superior')) {
|
|
697
|
-
return {
|
|
698
|
-
powerSwitch: enabled ? 1 : 0,
|
|
699
|
-
switchIdx: 0
|
|
700
|
-
};
|
|
701
|
-
} else {
|
|
702
|
-
return {
|
|
703
|
-
powerSwitch: enabled ? 1 : 0
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Toggle device power
|
|
710
|
-
*/
|
|
711
|
-
async toggleSwitch(enabled: boolean): Promise<boolean> {
|
|
712
|
-
logger.info(`Setting device power to ${enabled ? 'on' : 'off'}: ${this.deviceName}`);
|
|
713
|
-
const [response, status] = await this.callApi(
|
|
714
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
715
|
-
'post',
|
|
716
|
-
{
|
|
717
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
718
|
-
cid: this.cid,
|
|
719
|
-
configModule: this.configModule,
|
|
720
|
-
payload: {
|
|
721
|
-
data: this.getTogglePayload(enabled),
|
|
722
|
-
method: 'setSwitch',
|
|
723
|
-
source: 'APP'
|
|
724
|
-
}
|
|
725
|
-
},
|
|
726
|
-
Helpers.reqHeaderBypass()
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
const success = this.checkResponse([response, status], 'toggleSwitch');
|
|
730
|
-
if (success) {
|
|
731
|
-
this.deviceStatus = enabled ? 'on' : 'off';
|
|
732
|
-
} else {
|
|
733
|
-
logger.error(`Failed to set device power to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
734
|
-
}
|
|
735
|
-
return success;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* Set mist level
|
|
740
|
-
*/
|
|
741
|
-
async setMistLevel(level: number): Promise<boolean> {
|
|
742
|
-
if (!this.mistLevels.includes(level)) {
|
|
743
|
-
const error = `Invalid mist level: ${level}. Must be one of: ${this.mistLevels.join(', ')}`;
|
|
744
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
745
|
-
throw new Error(error);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
logger.info(`Setting mist level to ${level} for device: ${this.deviceName}`);
|
|
749
|
-
const [response, status] = await this.callApi(
|
|
750
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
751
|
-
'post',
|
|
752
|
-
{
|
|
753
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
754
|
-
cid: this.cid,
|
|
755
|
-
configModule: this.configModule,
|
|
756
|
-
payload: {
|
|
757
|
-
data: {
|
|
758
|
-
id: 0,
|
|
759
|
-
level: level,
|
|
760
|
-
type: 'mist'
|
|
761
|
-
},
|
|
762
|
-
method: 'setVirtualLevel',
|
|
763
|
-
source: 'APP'
|
|
764
|
-
}
|
|
765
|
-
},
|
|
766
|
-
Helpers.reqHeaderBypass()
|
|
767
|
-
);
|
|
768
|
-
|
|
769
|
-
const success = this.checkResponse([response, status], 'setMistLevel');
|
|
770
|
-
if (!success) {
|
|
771
|
-
logger.error(`Failed to set mist level to ${level} for device: ${this.deviceName}`);
|
|
772
|
-
}
|
|
773
|
-
return success;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* Change fan speed - Implemented to satisfy interface but redirects to setMistLevel
|
|
778
|
-
*/
|
|
779
|
-
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
780
|
-
logger.debug(`Redirecting fan speed change to mist level for device: ${this.deviceName}`);
|
|
781
|
-
return this.setMistLevel(speed);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Set device mode
|
|
786
|
-
*/
|
|
787
|
-
async setMode(mode: string): Promise<boolean> {
|
|
788
|
-
if (!this.modes.includes(mode as any)) {
|
|
789
|
-
const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
|
|
790
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
791
|
-
throw new Error(error);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
|
|
795
|
-
const [response, status] = await this.callApi(
|
|
796
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
797
|
-
'post',
|
|
798
|
-
{
|
|
799
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
800
|
-
cid: this.cid,
|
|
801
|
-
configModule: this.configModule,
|
|
802
|
-
payload: {
|
|
803
|
-
data: {
|
|
804
|
-
mode
|
|
805
|
-
},
|
|
806
|
-
method: 'setHumidityMode',
|
|
807
|
-
source: 'APP'
|
|
808
|
-
}
|
|
809
|
-
},
|
|
810
|
-
Helpers.reqHeaderBypass()
|
|
811
|
-
);
|
|
812
|
-
|
|
813
|
-
const success = this.checkResponse([response, status], 'setMode');
|
|
814
|
-
if (success) {
|
|
815
|
-
this.details.mode = mode;
|
|
816
|
-
} else {
|
|
817
|
-
logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
|
|
818
|
-
}
|
|
819
|
-
return success;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Set target humidity
|
|
824
|
-
*/
|
|
825
|
-
async setHumidity(humidity: number): Promise<boolean> {
|
|
826
|
-
if (!this.hasFeature('humidity')) {
|
|
827
|
-
const error = 'Humidity control not supported';
|
|
828
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
829
|
-
throw new Error(error);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (humidity < this.humidityRange.min || humidity > this.humidityRange.max) {
|
|
833
|
-
const error = `Invalid humidity: ${humidity}. Must be between ${this.humidityRange.min} and ${this.humidityRange.max}`;
|
|
834
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
835
|
-
throw new Error(error);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
logger.debug(`Setting target humidity to ${humidity}% for device: ${this.deviceName}`);
|
|
839
|
-
const [response, status] = await this.callApi(
|
|
840
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
841
|
-
'post',
|
|
842
|
-
{
|
|
843
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
844
|
-
cid: this.cid,
|
|
845
|
-
configModule: this.configModule,
|
|
846
|
-
payload: {
|
|
847
|
-
data: {
|
|
848
|
-
targetHumidity: humidity
|
|
849
|
-
},
|
|
850
|
-
method: 'setTargetHumidity',
|
|
851
|
-
source: 'APP'
|
|
852
|
-
}
|
|
853
|
-
},
|
|
854
|
-
Helpers.reqHeaderBypass()
|
|
855
|
-
);
|
|
856
|
-
|
|
857
|
-
const success = this.checkResponse([response, status], 'setHumidity');
|
|
858
|
-
if (!success) {
|
|
859
|
-
logger.error(`Failed to set target humidity to ${humidity}% for device: ${this.deviceName}`);
|
|
860
|
-
}
|
|
861
|
-
return success;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
* Set display status
|
|
866
|
-
*/
|
|
867
|
-
async setDisplay(enabled: boolean): Promise<boolean> {
|
|
868
|
-
if (!this.hasFeature('display')) {
|
|
869
|
-
const error = 'Display control not supported';
|
|
870
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
871
|
-
throw new Error(error);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
logger.debug(`Setting display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
875
|
-
const [response, status] = await this.callApi(
|
|
876
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
877
|
-
'post',
|
|
878
|
-
{
|
|
879
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
880
|
-
cid: this.cid,
|
|
881
|
-
configModule: this.configModule,
|
|
882
|
-
payload: {
|
|
883
|
-
data: {
|
|
884
|
-
state: enabled
|
|
885
|
-
},
|
|
886
|
-
method: 'setDisplay',
|
|
887
|
-
source: 'APP'
|
|
888
|
-
}
|
|
889
|
-
},
|
|
890
|
-
Helpers.reqHeaderBypass()
|
|
891
|
-
);
|
|
892
|
-
|
|
893
|
-
const success = this.checkResponse([response, status], 'setDisplay');
|
|
894
|
-
if (!success) {
|
|
895
|
-
logger.error(`Failed to set display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
896
|
-
}
|
|
897
|
-
return success;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* Set timer
|
|
902
|
-
*/
|
|
903
|
-
async setTimer(hours: number): Promise<boolean> {
|
|
904
|
-
if (!this.hasFeature('timer')) {
|
|
905
|
-
const error = 'Timer not supported';
|
|
906
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
907
|
-
throw new Error(error);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
logger.debug(`Setting timer to ${hours} hours for device: ${this.deviceName}`);
|
|
911
|
-
const [response, status] = await this.callApi(
|
|
912
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
913
|
-
'post',
|
|
914
|
-
{
|
|
915
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
916
|
-
cid: this.cid,
|
|
917
|
-
configModule: this.configModule,
|
|
918
|
-
payload: {
|
|
919
|
-
data: {
|
|
920
|
-
action: 'off',
|
|
921
|
-
total: hours * 3600
|
|
922
|
-
},
|
|
923
|
-
method: 'addTimer',
|
|
924
|
-
source: 'APP'
|
|
925
|
-
}
|
|
926
|
-
},
|
|
927
|
-
Helpers.reqHeaderBypass()
|
|
928
|
-
);
|
|
929
|
-
|
|
930
|
-
const success = this.checkResponse([response, status], 'setTimer');
|
|
931
|
-
if (!success) {
|
|
932
|
-
logger.error(`Failed to set timer to ${hours} hours for device: ${this.deviceName}`);
|
|
933
|
-
}
|
|
934
|
-
return success;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
/**
|
|
938
|
-
* Clear timer
|
|
939
|
-
*/
|
|
940
|
-
async clearTimer(): Promise<boolean> {
|
|
941
|
-
if (!this.hasFeature('timer')) {
|
|
942
|
-
const error = 'Timer not supported';
|
|
943
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
944
|
-
throw new Error(error);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
logger.debug(`Clearing timer for device: ${this.deviceName}`);
|
|
948
|
-
const [response, status] = await this.callApi(
|
|
949
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
950
|
-
'post',
|
|
951
|
-
{
|
|
952
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
953
|
-
cid: this.cid,
|
|
954
|
-
configModule: this.configModule,
|
|
955
|
-
payload: {
|
|
956
|
-
data: {},
|
|
957
|
-
method: 'deleteTimer',
|
|
958
|
-
source: 'APP'
|
|
959
|
-
}
|
|
960
|
-
},
|
|
961
|
-
Helpers.reqHeaderBypass()
|
|
962
|
-
);
|
|
963
|
-
|
|
964
|
-
const success = this.checkResponse([response, status], 'clearTimer');
|
|
965
|
-
if (!success) {
|
|
966
|
-
logger.error(`Failed to clear timer for device: ${this.deviceName}`);
|
|
967
|
-
}
|
|
968
|
-
return success;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* Turn automatic stop on
|
|
973
|
-
*/
|
|
974
|
-
async automaticStopOn(): Promise<boolean> {
|
|
975
|
-
logger.debug(`Setting automatic stop on for device: ${this.deviceName}`);
|
|
976
|
-
const [response, status] = await this.callApi(
|
|
977
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
978
|
-
'post',
|
|
979
|
-
{
|
|
980
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
981
|
-
cid: this.cid,
|
|
982
|
-
configModule: this.configModule,
|
|
983
|
-
payload: {
|
|
984
|
-
data: {
|
|
985
|
-
enabled: true
|
|
986
|
-
},
|
|
987
|
-
method: 'setAutomaticStop',
|
|
988
|
-
source: 'APP'
|
|
989
|
-
}
|
|
990
|
-
},
|
|
991
|
-
Helpers.reqHeaderBypass()
|
|
992
|
-
);
|
|
993
|
-
|
|
994
|
-
const success = this.checkResponse([response, status], 'automaticStopOn');
|
|
995
|
-
if (!success) {
|
|
996
|
-
logger.error(`Failed to set automatic stop on for device: ${this.deviceName}`);
|
|
997
|
-
}
|
|
998
|
-
return success;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* Turn automatic stop off
|
|
1003
|
-
*/
|
|
1004
|
-
async automaticStopOff(): Promise<boolean> {
|
|
1005
|
-
logger.debug(`Setting automatic stop off for device: ${this.deviceName}`);
|
|
1006
|
-
const [response, status] = await this.callApi(
|
|
1007
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1008
|
-
'post',
|
|
1009
|
-
{
|
|
1010
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
1011
|
-
cid: this.cid,
|
|
1012
|
-
configModule: this.configModule,
|
|
1013
|
-
payload: {
|
|
1014
|
-
data: {
|
|
1015
|
-
enabled: false
|
|
1016
|
-
},
|
|
1017
|
-
method: 'setAutomaticStop',
|
|
1018
|
-
source: 'APP'
|
|
1019
|
-
}
|
|
1020
|
-
},
|
|
1021
|
-
Helpers.reqHeaderBypass()
|
|
1022
|
-
);
|
|
1023
|
-
|
|
1024
|
-
const success = this.checkResponse([response, status], 'automaticStopOff');
|
|
1025
|
-
if (!success) {
|
|
1026
|
-
logger.error(`Failed to set automatic stop off for device: ${this.deviceName}`);
|
|
1027
|
-
}
|
|
1028
|
-
return success;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
/**
|
|
1032
|
-
* Set auto mode
|
|
1033
|
-
*/
|
|
1034
|
-
async setAutoMode(): Promise<boolean> {
|
|
1035
|
-
logger.debug(`Setting auto mode for device: ${this.deviceName}`);
|
|
1036
|
-
const success = await this.setMode('auto');
|
|
1037
|
-
if (!success) {
|
|
1038
|
-
logger.error(`Failed to set auto mode for device: ${this.deviceName}`);
|
|
1039
|
-
}
|
|
1040
|
-
return success;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* Set manual mode
|
|
1045
|
-
*/
|
|
1046
|
-
async setManualMode(): Promise<boolean> {
|
|
1047
|
-
logger.debug(`Setting manual mode for device: ${this.deviceName}`);
|
|
1048
|
-
const success = await this.setMode('manual');
|
|
1049
|
-
if (!success) {
|
|
1050
|
-
logger.error(`Failed to set manual mode for device: ${this.deviceName}`);
|
|
1051
|
-
}
|
|
1052
|
-
return success;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
/**
|
|
1056
|
-
* Turn off display
|
|
1057
|
-
*/
|
|
1058
|
-
async turnOffDisplay(): Promise<boolean> {
|
|
1059
|
-
logger.debug(`Turning off display for device: ${this.deviceName}`);
|
|
1060
|
-
const success = await this.setDisplay(false);
|
|
1061
|
-
if (!success) {
|
|
1062
|
-
logger.error(`Failed to turn off display for device: ${this.deviceName}`);
|
|
1063
|
-
}
|
|
1064
|
-
return success;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
/**
|
|
1068
|
-
* Turn on display
|
|
1069
|
-
*/
|
|
1070
|
-
async turnOnDisplay(): Promise<boolean> {
|
|
1071
|
-
logger.debug(`Turning on display for device: ${this.deviceName}`);
|
|
1072
|
-
const success = await this.setDisplay(true);
|
|
1073
|
-
if (!success) {
|
|
1074
|
-
logger.error(`Failed to turn on display for device: ${this.deviceName}`);
|
|
1075
|
-
}
|
|
1076
|
-
return success;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
/**
|
|
1081
|
-
* VeSync Warm Humidifier Class
|
|
1082
|
-
*/
|
|
1083
|
-
export class VeSyncWarmHumidifier extends VeSyncHumidifier {
|
|
1084
|
-
protected readonly warmLevels = [1, 2, 3];
|
|
1085
|
-
|
|
1086
|
-
constructor(details: Record<string, any>, manager: VeSync) {
|
|
1087
|
-
super(details, manager);
|
|
1088
|
-
logger.debug(`Initialized VeSyncWarmHumidifier device: ${this.deviceName}`);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
/**
|
|
1092
|
-
* Get device details
|
|
1093
|
-
*/
|
|
1094
|
-
async getDetails(): Promise<Boolean> {
|
|
1095
|
-
const success = await super.getDetails();
|
|
1096
|
-
if (success && this.details) {
|
|
1097
|
-
// Add warm humidifier specific fields
|
|
1098
|
-
this.details = {
|
|
1099
|
-
...this.details,
|
|
1100
|
-
warm_enabled: Boolean(this.details.warm_level && this.details.warm_level > 0),
|
|
1101
|
-
drying_mode_enabled: this.details.autoDryingSwitch === 1,
|
|
1102
|
-
drying_mode_state: this.details.dryingMode || '',
|
|
1103
|
-
drying_mode_level: this.details.dryingLevel || 0,
|
|
1104
|
-
drying_mode_seconds_remaining: this.details.dryingRemainTime || 0
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
return success;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
/**
|
|
1111
|
-
* Change fan speed - Implemented to satisfy interface but redirects to setMistLevel
|
|
1112
|
-
*/
|
|
1113
|
-
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
1114
|
-
logger.debug(`Redirecting fan speed change to mist level for device: ${this.deviceName}`);
|
|
1115
|
-
return this.setMistLevel(speed);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
/**
|
|
1119
|
-
* Set device mode
|
|
1120
|
-
*/
|
|
1121
|
-
async setMode(mode: string): Promise<boolean> {
|
|
1122
|
-
if (!this.modes.includes(mode as any)) {
|
|
1123
|
-
const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
|
|
1124
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1125
|
-
throw new Error(error);
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
|
|
1129
|
-
const [response, status] = await this.callApi(
|
|
1130
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1131
|
-
'post',
|
|
1132
|
-
{
|
|
1133
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
1134
|
-
cid: this.cid,
|
|
1135
|
-
configModule: this.configModule,
|
|
1136
|
-
payload: {
|
|
1137
|
-
data: {
|
|
1138
|
-
mode
|
|
1139
|
-
},
|
|
1140
|
-
method: 'setHumidityMode',
|
|
1141
|
-
source: 'APP'
|
|
1142
|
-
}
|
|
1143
|
-
},
|
|
1144
|
-
Helpers.reqHeaderBypass()
|
|
1145
|
-
);
|
|
1146
|
-
|
|
1147
|
-
const success = this.checkResponse([response, status], 'setMode');
|
|
1148
|
-
if (success) {
|
|
1149
|
-
this.details.mode = mode;
|
|
1150
|
-
} else {
|
|
1151
|
-
logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
|
|
1152
|
-
}
|
|
1153
|
-
return success;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
/**
|
|
1157
|
-
* Set warm mist level
|
|
1158
|
-
*/
|
|
1159
|
-
async setWarmLevel(level: number): Promise<boolean> {
|
|
1160
|
-
if (!this.hasFeature('warm')) {
|
|
1161
|
-
const error = 'Warm mist control not supported';
|
|
1162
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1163
|
-
throw new Error(error);
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
if (!this.warmLevels.includes(level)) {
|
|
1167
|
-
const error = `Invalid warm level: ${level}. Must be one of: ${this.warmLevels.join(', ')}`;
|
|
1168
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1169
|
-
throw new Error(error);
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
logger.debug(`Setting warm level to ${level} for device: ${this.deviceName}`);
|
|
1173
|
-
const [response, status] = await this.callApi(
|
|
1174
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1175
|
-
'post',
|
|
1176
|
-
{
|
|
1177
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
1178
|
-
cid: this.cid,
|
|
1179
|
-
configModule: this.configModule,
|
|
1180
|
-
payload: {
|
|
1181
|
-
data: {
|
|
1182
|
-
warmLevel: level
|
|
1183
|
-
},
|
|
1184
|
-
method: 'setWarmLevel',
|
|
1185
|
-
source: 'APP'
|
|
1186
|
-
}
|
|
1187
|
-
},
|
|
1188
|
-
Helpers.reqHeaderBypass()
|
|
1189
|
-
);
|
|
1190
|
-
|
|
1191
|
-
const success = this.checkResponse([response, status], 'setWarmLevel');
|
|
1192
|
-
if (success) {
|
|
1193
|
-
this.details.warm_level = level;
|
|
1194
|
-
this.details.warm_enabled = level > 0;
|
|
1195
|
-
} else {
|
|
1196
|
-
logger.error(`Failed to set warm level to ${level} for device: ${this.deviceName}`);
|
|
1197
|
-
}
|
|
1198
|
-
return success;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
/**
|
|
1202
|
-
* Set drying mode enabled
|
|
1203
|
-
*/
|
|
1204
|
-
async setDryingModeEnabled(enabled: boolean): Promise<boolean> {
|
|
1205
|
-
if (!this.hasFeature('drying')) {
|
|
1206
|
-
const error = 'Drying mode not supported';
|
|
1207
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1208
|
-
throw new Error(error);
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
logger.debug(`Setting drying mode to ${enabled} for device: ${this.deviceName}`);
|
|
1212
|
-
const [response, status] = await this.callApi(
|
|
1213
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1214
|
-
'post',
|
|
1215
|
-
{
|
|
1216
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
1217
|
-
cid: this.cid,
|
|
1218
|
-
configModule: this.configModule,
|
|
1219
|
-
payload: {
|
|
1220
|
-
data: {
|
|
1221
|
-
autoDryingSwitch: enabled ? 1 : 0
|
|
1222
|
-
},
|
|
1223
|
-
method: 'setDryingMode',
|
|
1224
|
-
source: 'APP'
|
|
1225
|
-
}
|
|
1226
|
-
},
|
|
1227
|
-
Helpers.reqHeaderBypass()
|
|
1228
|
-
);
|
|
1229
|
-
|
|
1230
|
-
const success = this.checkResponse([response, status], 'setDryingModeEnabled');
|
|
1231
|
-
if (success) {
|
|
1232
|
-
this.details.drying_mode_enabled = enabled;
|
|
1233
|
-
this.details.autoDryingSwitch = enabled ? 1 : 0;
|
|
1234
|
-
} else {
|
|
1235
|
-
logger.error(`Failed to set drying mode to ${enabled} for device: ${this.deviceName}`);
|
|
1236
|
-
}
|
|
1237
|
-
return success;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
/**
|
|
1241
|
-
* Get warm mist enabled status
|
|
1242
|
-
*/
|
|
1243
|
-
get warmMistEnabled(): boolean {
|
|
1244
|
-
return this.details.warm_enabled || false;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
/**
|
|
1248
|
-
* Get drying mode enabled status
|
|
1249
|
-
*/
|
|
1250
|
-
get dryingModeEnabled(): boolean {
|
|
1251
|
-
return this.details.drying_mode_enabled || false;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
/**
|
|
1255
|
-
* Get drying mode state
|
|
1256
|
-
*/
|
|
1257
|
-
get dryingModeState(): string {
|
|
1258
|
-
return this.details.drying_mode_state || '';
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
/**
|
|
1262
|
-
* Get drying mode level
|
|
1263
|
-
*/
|
|
1264
|
-
get dryingModeLevel(): number {
|
|
1265
|
-
return this.details.drying_mode_level || 0;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
/**
|
|
1269
|
-
* Get drying mode seconds remaining
|
|
1270
|
-
*/
|
|
1271
|
-
get dryingModeSecondsRemaining(): number {
|
|
1272
|
-
return this.details.drying_mode_seconds_remaining || 0;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
/**
|
|
1277
|
-
* VeSync Air Purifier with Bypass V2
|
|
1278
|
-
*/
|
|
1279
|
-
export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
1280
|
-
protected _lightDetection: boolean = false;
|
|
1281
|
-
protected _lightDetectionState: boolean = false;
|
|
1282
|
-
protected setSpeedLevel: number | null = null;
|
|
1283
|
-
protected autoPreferences: string[] = ['default', 'efficient', 'quiet'];
|
|
1284
|
-
protected enabled: boolean = false;
|
|
1285
|
-
protected _mode: string = '';
|
|
1286
|
-
protected _speed: number = 0;
|
|
1287
|
-
protected _timer: { duration: number; action: string } | null = null;
|
|
1288
|
-
|
|
1289
|
-
constructor(details: Record<string, any>, manager: VeSync) {
|
|
1290
|
-
super(details, manager);
|
|
1291
|
-
logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
protected buildConfigDict(configDict: Record<string, any>): void {
|
|
1295
|
-
if (configDict) {
|
|
1296
|
-
this.config = {
|
|
1297
|
-
...this.config, // Preserve existing config
|
|
1298
|
-
...configDict // Merge in new config
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
get mode(): string {
|
|
1304
|
-
return this._mode;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
set mode(value: string) {
|
|
1308
|
-
this._mode = value;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
get speed(): number {
|
|
1312
|
-
return this._speed;
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
set speed(value: number) {
|
|
1316
|
-
this._speed = value;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
get timer(): { duration: number; action: string } | null {
|
|
1320
|
-
return this._timer;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
set timer(value: { duration: number; action: string } | null) {
|
|
1324
|
-
this._timer = value;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
/**
|
|
1328
|
-
* Build API dictionary
|
|
1329
|
-
*/
|
|
1330
|
-
protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
|
|
1331
|
-
const head = Helpers.reqHeaderBypass();
|
|
1332
|
-
const body = {
|
|
1333
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
1334
|
-
cid: this.cid,
|
|
1335
|
-
configModule: this.configModule,
|
|
1336
|
-
payload: {
|
|
1337
|
-
method,
|
|
1338
|
-
source: 'APP',
|
|
1339
|
-
data: {}
|
|
1340
|
-
}
|
|
1341
|
-
};
|
|
1342
|
-
return [head, body];
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
/**
|
|
1346
|
-
* Get device details
|
|
1347
|
-
*/
|
|
1348
|
-
async getDetails(): Promise<Boolean> {
|
|
1349
|
-
logger.debug(`Getting details for device: ${this.deviceName}`);
|
|
1350
|
-
const [head, body] = this.buildApiDict('getPurifierStatus');
|
|
1351
|
-
|
|
1352
|
-
const [response, status] = await this.callApi(
|
|
1353
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1354
|
-
'post',
|
|
1355
|
-
body,
|
|
1356
|
-
head
|
|
1357
|
-
);
|
|
1358
|
-
|
|
1359
|
-
if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
|
|
1360
|
-
logger.debug('Error getting purifier details');
|
|
1361
|
-
this.connectionStatus = 'offline';
|
|
1362
|
-
return false;
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
const innerResponse = response.result;
|
|
1366
|
-
if (innerResponse.code !== 0 || !innerResponse.result) {
|
|
1367
|
-
logger.debug('Error in inner response from purifier');
|
|
1368
|
-
this.connectionStatus = 'offline';
|
|
1369
|
-
return false;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
const deviceData = innerResponse.result;
|
|
1373
|
-
this.buildPurifierDict(deviceData);
|
|
1374
|
-
if (deviceData.configuration) {
|
|
1375
|
-
this.buildConfigDict(deviceData.configuration);
|
|
1376
|
-
}
|
|
1377
|
-
return true;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
/**
|
|
1381
|
-
* Build purifier dictionary
|
|
1382
|
-
*/
|
|
1383
|
-
protected buildPurifierDict(devDict: Record<string, any>): void {
|
|
1384
|
-
this.connectionStatus = 'online';
|
|
1385
|
-
const powerSwitch = Boolean(devDict.powerSwitch ?? 0);
|
|
1386
|
-
this.enabled = powerSwitch;
|
|
1387
|
-
this.deviceStatus = powerSwitch ? 'on' : 'off';
|
|
1388
|
-
this.mode = devDict.workMode ?? 'manual';
|
|
1389
|
-
|
|
1390
|
-
this.speed = devDict.fanSpeedLevel ?? 0;
|
|
1391
|
-
this.setSpeedLevel = devDict.manualSpeedLevel ?? 1;
|
|
1392
|
-
|
|
1393
|
-
this.details = {
|
|
1394
|
-
...this.details,
|
|
1395
|
-
filter_life: devDict.filterLifePercent ?? 0,
|
|
1396
|
-
child_lock: Boolean(devDict.childLockSwitch ?? 0),
|
|
1397
|
-
display: Boolean(devDict.screenState ?? 0),
|
|
1398
|
-
light_detection_switch: Boolean(devDict.lightDetectionSwitch ?? 0),
|
|
1399
|
-
environment_light_state: Boolean(devDict.environmentLightState ?? 0),
|
|
1400
|
-
screen_switch: Boolean(devDict.screenSwitch ?? 0)
|
|
1401
|
-
};
|
|
1402
|
-
|
|
1403
|
-
if (this.hasFeature('air_quality')) {
|
|
1404
|
-
this.details.air_quality_value = devDict.PM25 ?? 0;
|
|
1405
|
-
this.details.air_quality = devDict.AQLevel ?? 0;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
if ('PM1' in devDict) this.details.pm1 = devDict.PM1;
|
|
1409
|
-
if ('PM10' in devDict) this.details.pm10 = devDict.PM10;
|
|
1410
|
-
if ('AQPercent' in devDict) this.details.aq_percent = devDict.AQPercent;
|
|
1411
|
-
if ('fanRotateAngle' in devDict) this.details.fan_rotate_angle = devDict.fanRotateAngle;
|
|
1412
|
-
if ('filterOpenState' in devDict) this.details.filter_open_state = Boolean(devDict.filterOpenState);
|
|
1413
|
-
|
|
1414
|
-
if ((devDict.timerRemain ?? 0) > 0) {
|
|
1415
|
-
this.timer = { duration: devDict.timerRemain, action: 'off' };
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
if (typeof devDict.autoPreference === 'object' && devDict.autoPreference) {
|
|
1419
|
-
this.details.auto_preference_type = devDict.autoPreference?.autoPreferenceType ?? 'default';
|
|
1420
|
-
} else {
|
|
1421
|
-
this.details.auto_preference_type = null;
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
/**
|
|
1426
|
-
* Toggle device power
|
|
1427
|
-
*/
|
|
1428
|
-
async toggleSwitch(toggle: boolean): Promise<boolean> {
|
|
1429
|
-
if (typeof toggle !== 'boolean') {
|
|
1430
|
-
logger.debug('Invalid toggle value for purifier switch');
|
|
1431
|
-
return false;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
const [head, body] = this.buildApiDict('setSwitch');
|
|
1435
|
-
body.payload.data = {
|
|
1436
|
-
powerSwitch: toggle ? 1 : 0,
|
|
1437
|
-
switchIdx: 0
|
|
1438
|
-
};
|
|
1439
|
-
|
|
1440
|
-
const [response, status] = await this.callApi(
|
|
1441
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1442
|
-
'post',
|
|
1443
|
-
body,
|
|
1444
|
-
head
|
|
1445
|
-
);
|
|
1446
|
-
|
|
1447
|
-
const success = this.checkResponse([response, status], 'toggleSwitch');
|
|
1448
|
-
if (success) {
|
|
1449
|
-
this.deviceStatus = toggle ? 'on' : 'off';
|
|
1450
|
-
}
|
|
1451
|
-
return success;
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
/**
|
|
1455
|
-
* Turn device on - compatibility method that proxies to toggleSwitch
|
|
1456
|
-
*/
|
|
1457
|
-
async turnOn(): Promise<boolean> {
|
|
1458
|
-
logger.info(`Turning on device: ${this.deviceName}`);
|
|
1459
|
-
return this.toggleSwitch(true);
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
/**
|
|
1463
|
-
* Turn device off - compatibility method that proxies to toggleSwitch
|
|
1464
|
-
*/
|
|
1465
|
-
async turnOff(): Promise<boolean> {
|
|
1466
|
-
logger.info(`Turning off device: ${this.deviceName}`);
|
|
1467
|
-
return this.toggleSwitch(false);
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
/**
|
|
1471
|
-
* Change fan speed
|
|
1472
|
-
*/
|
|
1473
|
-
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
1474
|
-
// const speeds = this.config.levels ?? [];
|
|
1475
|
-
// if (!speeds.includes(speed)) {
|
|
1476
|
-
// logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
|
|
1477
|
-
// return false;
|
|
1478
|
-
// }
|
|
1479
|
-
|
|
1480
|
-
logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
|
|
1481
|
-
const [response, status] = await this.callApi(
|
|
1482
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1483
|
-
'post',
|
|
1484
|
-
{
|
|
1485
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
1486
|
-
cid: this.cid,
|
|
1487
|
-
configModule: this.configModule,
|
|
1488
|
-
payload: {
|
|
1489
|
-
data: {
|
|
1490
|
-
id: 0,
|
|
1491
|
-
level: speed,
|
|
1492
|
-
mode: 'manual',
|
|
1493
|
-
type: 'wind'
|
|
1494
|
-
},
|
|
1495
|
-
method: 'setLevel',
|
|
1496
|
-
source: 'APP'
|
|
1497
|
-
}
|
|
1498
|
-
},
|
|
1499
|
-
Helpers.reqHeaderBypass()
|
|
1500
|
-
);
|
|
1501
|
-
|
|
1502
|
-
const success = this.checkResponse([response, status], 'changeFanSpeed');
|
|
1503
|
-
if (success) {
|
|
1504
|
-
this.setSpeedLevel = speed;
|
|
1505
|
-
this.mode = 'manual';
|
|
1506
|
-
}
|
|
1507
|
-
return success;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
/**
|
|
1511
|
-
* Set mode
|
|
1512
|
-
*/
|
|
1513
|
-
async setMode(mode: string): Promise<boolean> {
|
|
1514
|
-
if (!this.modes.includes(mode as any)) {
|
|
1515
|
-
logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`);
|
|
1516
|
-
return false;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
if (mode === 'manual') {
|
|
1520
|
-
if (!this.speed || this.speed === 0) {
|
|
1521
|
-
return this.changeFanSpeed(1);
|
|
1522
|
-
}
|
|
1523
|
-
return this.changeFanSpeed(this.speed);
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
if (mode === 'off') {
|
|
1527
|
-
return this.turnOff();
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
const [head, body] = this.buildApiDict('setPurifierMode');
|
|
1531
|
-
body.payload.data = {
|
|
1532
|
-
workMode: mode
|
|
1533
|
-
};
|
|
1534
|
-
|
|
1535
|
-
const [response, status] = await this.callApi(
|
|
1536
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1537
|
-
'post',
|
|
1538
|
-
body,
|
|
1539
|
-
head
|
|
1540
|
-
);
|
|
1541
|
-
|
|
1542
|
-
const success = this.checkResponse([response, status], 'setMode');
|
|
1543
|
-
if (success) {
|
|
1544
|
-
this.mode = mode;
|
|
1545
|
-
}
|
|
1546
|
-
return success;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
/**
|
|
1550
|
-
* Set display status
|
|
1551
|
-
*/
|
|
1552
|
-
async setDisplay(enabled: boolean): Promise<boolean> {
|
|
1553
|
-
if (!this.hasFeature('display')) {
|
|
1554
|
-
const error = 'Display control not supported';
|
|
1555
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1556
|
-
throw new Error(error);
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
const [head, body] = this.buildApiDict('setDisplay');
|
|
1560
|
-
body.payload.data = {
|
|
1561
|
-
screenSwitch: enabled ? 1 : 0
|
|
1562
|
-
};
|
|
1563
|
-
|
|
1564
|
-
const [response, status] = await this.callApi(
|
|
1565
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1566
|
-
'post',
|
|
1567
|
-
body,
|
|
1568
|
-
head
|
|
1569
|
-
);
|
|
1570
|
-
|
|
1571
|
-
const success = this.checkResponse([response, status], 'setDisplay');
|
|
1572
|
-
if (success) {
|
|
1573
|
-
this.details.screen_switch = enabled;
|
|
1574
|
-
}
|
|
1575
|
-
return success;
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
/**
|
|
1579
|
-
* Set child lock
|
|
1580
|
-
*/
|
|
1581
|
-
async setChildLock(enabled: boolean): Promise<boolean> {
|
|
1582
|
-
if (!this.hasFeature('child_lock')) {
|
|
1583
|
-
const error = 'Child lock not supported';
|
|
1584
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1585
|
-
throw new Error(error);
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
const [head, body] = this.buildApiDict('setChildLock');
|
|
1589
|
-
body.payload.data = {
|
|
1590
|
-
childLockSwitch: enabled ? 1 : 0
|
|
1591
|
-
};
|
|
1592
|
-
|
|
1593
|
-
const [response, status] = await this.callApi(
|
|
1594
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1595
|
-
'post',
|
|
1596
|
-
body,
|
|
1597
|
-
head
|
|
1598
|
-
);
|
|
1599
|
-
|
|
1600
|
-
const success = this.checkResponse([response, status], 'setChildLock');
|
|
1601
|
-
if (success) {
|
|
1602
|
-
this.details.child_lock = enabled;
|
|
1603
|
-
}
|
|
1604
|
-
return success;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
/**
|
|
1608
|
-
* Set timer
|
|
1609
|
-
*/
|
|
1610
|
-
async setTimer(timerDuration: number, action: 'on' | 'off' = 'off', method: string = 'powerSwitch'): Promise<boolean> {
|
|
1611
|
-
if (!this.hasFeature('timer')) {
|
|
1612
|
-
const error = 'Timer not supported';
|
|
1613
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1614
|
-
throw new Error(error);
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
if (!['on', 'off'].includes(action)) {
|
|
1618
|
-
logger.debug('Invalid action for timer');
|
|
1619
|
-
return false;
|
|
1620
|
-
}
|
|
1621
|
-
if (method !== 'powerSwitch') {
|
|
1622
|
-
logger.debug('Invalid method for timer');
|
|
1623
|
-
return false;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
const [head, body] = this.buildApiDict('addTimerV2');
|
|
1627
|
-
body.payload.subDeviceNo = 0;
|
|
1628
|
-
body.payload.data = {
|
|
1629
|
-
startAct: [{
|
|
1630
|
-
type: method,
|
|
1631
|
-
num: 0,
|
|
1632
|
-
act: action === 'on' ? 1 : 0
|
|
1633
|
-
}],
|
|
1634
|
-
total: timerDuration,
|
|
1635
|
-
subDeviceNo: 0
|
|
1636
|
-
};
|
|
1637
|
-
|
|
1638
|
-
const [response, status] = await this.callApi(
|
|
1639
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1640
|
-
'post',
|
|
1641
|
-
body,
|
|
1642
|
-
head
|
|
1643
|
-
);
|
|
1644
|
-
|
|
1645
|
-
const success = this.checkResponse([response, status], 'setTimer');
|
|
1646
|
-
if (success) {
|
|
1647
|
-
this.timer = { duration: timerDuration, action };
|
|
1648
|
-
}
|
|
1649
|
-
return success;
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
/**
|
|
1653
|
-
* Clear timer
|
|
1654
|
-
*/
|
|
1655
|
-
async clearTimer(): Promise<boolean> {
|
|
1656
|
-
if (!this.hasFeature('timer')) {
|
|
1657
|
-
const error = 'Timer not supported';
|
|
1658
|
-
logger.error(`${error} for device: ${this.deviceName}`);
|
|
1659
|
-
throw new Error(error);
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
const [head, body] = this.buildApiDict('delTimerV2');
|
|
1663
|
-
body.payload.subDeviceNo = 0;
|
|
1664
|
-
body.payload.data = { id: 1, subDeviceNo: 0 };
|
|
1665
|
-
|
|
1666
|
-
const [response, status] = await this.callApi(
|
|
1667
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1668
|
-
'post',
|
|
1669
|
-
body,
|
|
1670
|
-
head
|
|
1671
|
-
);
|
|
1672
|
-
|
|
1673
|
-
const success = this.checkResponse([response, status], 'clearTimer');
|
|
1674
|
-
if (success) {
|
|
1675
|
-
this.timer = null;
|
|
1676
|
-
}
|
|
1677
|
-
return success;
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
/**
|
|
1681
|
-
* Set auto preference
|
|
1682
|
-
*/
|
|
1683
|
-
async setAutoPreference(preference: string = 'default', roomSize: number = 600): Promise<boolean> {
|
|
1684
|
-
if (!this.autoPreferences.includes(preference)) {
|
|
1685
|
-
logger.debug(`Invalid preference: ${preference} - valid preferences are default, efficient, quiet`);
|
|
1686
|
-
return false;
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
const [head, body] = this.buildApiDict('setAutoPreference');
|
|
1690
|
-
body.payload.data = {
|
|
1691
|
-
autoPreference: preference,
|
|
1692
|
-
roomSize: roomSize
|
|
1693
|
-
};
|
|
1694
|
-
|
|
1695
|
-
const [response, status] = await this.callApi(
|
|
1696
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1697
|
-
'post',
|
|
1698
|
-
body,
|
|
1699
|
-
head
|
|
1700
|
-
);
|
|
1701
|
-
|
|
1702
|
-
const success = this.checkResponse([response, status], 'setAutoPreference');
|
|
1703
|
-
if (success) {
|
|
1704
|
-
this.details.auto_preference = preference;
|
|
1705
|
-
}
|
|
1706
|
-
return success;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
/**
|
|
1710
|
-
* Set light detection
|
|
1711
|
-
*/
|
|
1712
|
-
async setLightDetection(enabled: boolean): Promise<boolean> {
|
|
1713
|
-
const [head, body] = this.buildApiDict('setLightDetection');
|
|
1714
|
-
body.payload.data = {
|
|
1715
|
-
lightDetectionSwitch: enabled ? 1 : 0
|
|
1716
|
-
};
|
|
1717
|
-
|
|
1718
|
-
const [response, status] = await this.callApi(
|
|
1719
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1720
|
-
'post',
|
|
1721
|
-
body,
|
|
1722
|
-
head
|
|
1723
|
-
);
|
|
1724
|
-
|
|
1725
|
-
const success = this.checkResponse([response, status], 'setLightDetection');
|
|
1726
|
-
if (success) {
|
|
1727
|
-
this.details.light_detection_switch = enabled;
|
|
1728
|
-
}
|
|
1729
|
-
return success;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
/**
|
|
1733
|
-
* Get light detection status
|
|
1734
|
-
*/
|
|
1735
|
-
get lightDetection(): boolean {
|
|
1736
|
-
return Boolean(this.details.light_detection_switch);
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
/**
|
|
1740
|
-
* Get light detection state
|
|
1741
|
-
*/
|
|
1742
|
-
get lightDetectionState(): boolean {
|
|
1743
|
-
return Boolean(this.details.environment_light_state);
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
/**
|
|
1747
|
-
* Get auto preference type
|
|
1748
|
-
*/
|
|
1749
|
-
get autoPreferenceType(): string | null {
|
|
1750
|
-
return this.details.auto_preference_type;
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
/**
|
|
1754
|
-
* Display JSON details
|
|
1755
|
-
*/
|
|
1756
|
-
override displayJSON(): string {
|
|
1757
|
-
const baseInfo = JSON.parse(super.displayJSON());
|
|
1758
|
-
const details: Record<string, string> = {
|
|
1759
|
-
...baseInfo,
|
|
1760
|
-
'Mode': this.mode,
|
|
1761
|
-
'Filter Life': (this.details.filter_life ?? 0).toString(),
|
|
1762
|
-
'Fan Level': this.speed.toString(),
|
|
1763
|
-
'Display On': this.details.display?.toString() ?? 'false',
|
|
1764
|
-
'Child Lock': (this.details.child_lock ?? false).toString(),
|
|
1765
|
-
'Night Light': (this.details.night_light ?? '').toString(),
|
|
1766
|
-
'Display Set On': (this.details.screen_switch ?? false).toString(),
|
|
1767
|
-
'Light Detection Enabled': (this.details.light_detection_switch ?? false).toString(),
|
|
1768
|
-
'Environment Light State': (this.details.environment_light_state ?? false).toString()
|
|
1769
|
-
};
|
|
1770
|
-
|
|
1771
|
-
if (this.hasFeature('air_quality')) {
|
|
1772
|
-
details['Air Quality Level'] = (this.details.air_quality ?? '').toString();
|
|
1773
|
-
details['Air Quality Value'] = (this.details.air_quality_value ?? '').toString();
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
const everestKeys: Record<string, string> = {
|
|
1777
|
-
'pm1': 'PM1',
|
|
1778
|
-
'pm10': 'PM10',
|
|
1779
|
-
'fan_rotate_angle': 'Fan Rotate Angle',
|
|
1780
|
-
'filter_open_state': 'Filter Open State'
|
|
1781
|
-
};
|
|
1782
|
-
|
|
1783
|
-
for (const [key, value] of Object.entries(everestKeys)) {
|
|
1784
|
-
if (key in this.details) {
|
|
1785
|
-
details[value] = this.details[key]?.toString() ?? '';
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
return JSON.stringify(details, null, 4);
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
/**
|
|
1793
|
-
* Set oscillation state
|
|
1794
|
-
*/
|
|
1795
|
-
async setOscillation(toggle: boolean): Promise<boolean> {
|
|
1796
|
-
logger.debug(`Setting oscillation to ${toggle ? 'on' : 'off'} for device: ${this.deviceName}`);
|
|
1797
|
-
|
|
1798
|
-
const [head, body] = this.buildApiDict('setSwitch');
|
|
1799
|
-
body.payload.data = {
|
|
1800
|
-
oscillationSwitch: toggle ? 1 : 0,
|
|
1801
|
-
switchIdx: 0
|
|
1802
|
-
};
|
|
1803
|
-
|
|
1804
|
-
const [response, status] = await this.callApi(
|
|
1805
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1806
|
-
'post',
|
|
1807
|
-
body,
|
|
1808
|
-
head
|
|
1809
|
-
);
|
|
1810
|
-
|
|
1811
|
-
const success = this.checkResponse([response, status], 'setOscillation');
|
|
1812
|
-
if (success) {
|
|
1813
|
-
this.details.oscillationState = toggle;
|
|
1814
|
-
this.details.oscillationSwitch = toggle ? 1 : 0;
|
|
1815
|
-
}
|
|
1816
|
-
return success;
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
/**
|
|
1821
|
-
* VeSync Tower Fan Implementation
|
|
1822
|
-
*/
|
|
1823
|
-
export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
1824
|
-
protected readonly towerModes = ['normal', 'advancedSleep', 'off'] as const;
|
|
1825
|
-
|
|
1826
|
-
constructor(details: Record<string, any>, manager: VeSync) {
|
|
1827
|
-
super(details, manager);
|
|
1828
|
-
logger.debug(`Initialized VeSyncTowerFan device: ${this.deviceName}`);
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
/**
|
|
1832
|
-
* Get timer status (in seconds)
|
|
1833
|
-
*/
|
|
1834
|
-
override get timer(): { duration: number; action: string } | null {
|
|
1835
|
-
const timerRemain = this.details.timerRemain || 0;
|
|
1836
|
-
return timerRemain > 0 ? { duration: timerRemain, action: 'off' } : null;
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
override set timer(value: { duration: number; action: string } | null) {
|
|
1840
|
-
if (value) {
|
|
1841
|
-
this.details.timerRemain = value.duration;
|
|
1842
|
-
} else {
|
|
1843
|
-
this.details.timerRemain = 0;
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
/**
|
|
1848
|
-
* Build API dictionary
|
|
1849
|
-
*/
|
|
1850
|
-
protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
|
|
1851
|
-
const head = Helpers.reqHeaderBypass();
|
|
1852
|
-
const body = {
|
|
1853
|
-
...Helpers.reqBody(this.manager, 'bypassV2'),
|
|
1854
|
-
cid: this.cid,
|
|
1855
|
-
configModule: this.configModule,
|
|
1856
|
-
payload: {
|
|
1857
|
-
method,
|
|
1858
|
-
source: 'APP',
|
|
1859
|
-
data: {}
|
|
1860
|
-
}
|
|
1861
|
-
};
|
|
1862
|
-
return [head, body];
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
/**
|
|
1866
|
-
* Get device details
|
|
1867
|
-
*/
|
|
1868
|
-
async getDetails(): Promise<Boolean> {
|
|
1869
|
-
logger.debug(`Getting details for device: ${this.deviceName}`);
|
|
1870
|
-
const [head, body] = this.buildApiDict('getTowerFanStatus');
|
|
1871
|
-
|
|
1872
|
-
const [response, status] = await this.callApi(
|
|
1873
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1874
|
-
'post',
|
|
1875
|
-
body,
|
|
1876
|
-
head
|
|
1877
|
-
);
|
|
1878
|
-
|
|
1879
|
-
if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
|
|
1880
|
-
logger.debug('Error getting tower fan details');
|
|
1881
|
-
this.connectionStatus = 'offline';
|
|
1882
|
-
return false;
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
const innerResponse = response.result;
|
|
1886
|
-
if (innerResponse.code !== 0 || !innerResponse.result) {
|
|
1887
|
-
logger.debug('Error in inner response from tower fan');
|
|
1888
|
-
this.connectionStatus = 'offline';
|
|
1889
|
-
return false;
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
const deviceData = innerResponse.result;
|
|
1893
|
-
this.deviceStatus = deviceData.powerSwitch === 1 ? 'on' : 'off';
|
|
1894
|
-
this.details = {
|
|
1895
|
-
powerSwitch: deviceData.powerSwitch,
|
|
1896
|
-
workMode: deviceData.workMode,
|
|
1897
|
-
manualSpeedLevel: deviceData.manualSpeedLevel,
|
|
1898
|
-
fanSpeedLevel: deviceData.fanSpeedLevel,
|
|
1899
|
-
screenState: deviceData.screenState,
|
|
1900
|
-
screenSwitch: deviceData.screenSwitch,
|
|
1901
|
-
oscillationState: deviceData.oscillationState,
|
|
1902
|
-
oscillationSwitch: deviceData.oscillationSwitch,
|
|
1903
|
-
timerRemain: deviceData.timerRemain,
|
|
1904
|
-
temperature: deviceData.temperature || 0,
|
|
1905
|
-
humidity: deviceData.humidity || 0,
|
|
1906
|
-
thermalComfort: deviceData.thermalComfort,
|
|
1907
|
-
sleepPreference: deviceData.sleepPreference || {},
|
|
1908
|
-
scheduleCount: deviceData.scheduleCount,
|
|
1909
|
-
displayingType: deviceData.displayingType,
|
|
1910
|
-
errorCode: deviceData.errorCode
|
|
1911
|
-
};
|
|
1912
|
-
|
|
1913
|
-
logger.debug(`Successfully got details for device: ${this.deviceName}`);
|
|
1914
|
-
return true;
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
/**
|
|
1918
|
-
* Toggle device power
|
|
1919
|
-
*/
|
|
1920
|
-
async toggleSwitch(toggle: boolean): Promise<boolean> {
|
|
1921
|
-
if (typeof toggle !== 'boolean') {
|
|
1922
|
-
logger.debug('Invalid toggle value for tower fan switch');
|
|
1923
|
-
return false;
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
const [head, body] = this.buildApiDict('setSwitch');
|
|
1927
|
-
body.payload.data = {
|
|
1928
|
-
powerSwitch: toggle ? 1 : 0,
|
|
1929
|
-
switchIdx: 0
|
|
1930
|
-
};
|
|
1931
|
-
|
|
1932
|
-
const [response, status] = await this.callApi(
|
|
1933
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1934
|
-
'post',
|
|
1935
|
-
body,
|
|
1936
|
-
head
|
|
1937
|
-
);
|
|
1938
|
-
|
|
1939
|
-
const success = this.checkResponse([response, status], 'toggleSwitch');
|
|
1940
|
-
if (success) {
|
|
1941
|
-
this.deviceStatus = toggle ? 'on' : 'off';
|
|
1942
|
-
} else {
|
|
1943
|
-
logger.debug('Error toggling tower fan power');
|
|
1944
|
-
}
|
|
1945
|
-
return success;
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
/**
|
|
1949
|
-
* Turn device on
|
|
1950
|
-
*/
|
|
1951
|
-
async turnOn(): Promise<boolean> {
|
|
1952
|
-
logger.debug(`Turning on device: ${this.deviceName}`);
|
|
1953
|
-
return this.toggleSwitch(true);
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
/**
|
|
1957
|
-
* Turn device off
|
|
1958
|
-
*/
|
|
1959
|
-
async turnOff(): Promise<boolean> {
|
|
1960
|
-
logger.debug(`Turning off device: ${this.deviceName}`);
|
|
1961
|
-
return this.toggleSwitch(false);
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
/**
|
|
1965
|
-
* Set tower fan mode
|
|
1966
|
-
*/
|
|
1967
|
-
async mode_toggle(mode: string): Promise<boolean> {
|
|
1968
|
-
if (!this.towerModes.includes(mode as any)) {
|
|
1969
|
-
logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.towerModes.join(', ')}`);
|
|
1970
|
-
return false;
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
if (mode === 'off') {
|
|
1974
|
-
return this.turnOff();
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
const [head, body] = this.buildApiDict('setTowerFanMode');
|
|
1978
|
-
body.deviceId = this.cid;
|
|
1979
|
-
body.payload.data = {
|
|
1980
|
-
workMode: mode
|
|
1981
|
-
};
|
|
1982
|
-
|
|
1983
|
-
const [response, status] = await this.callApi(
|
|
1984
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
1985
|
-
'post',
|
|
1986
|
-
body,
|
|
1987
|
-
head
|
|
1988
|
-
);
|
|
1989
|
-
|
|
1990
|
-
const success = this.checkResponse([response, status], 'mode_toggle');
|
|
1991
|
-
if (success) {
|
|
1992
|
-
this.details.workMode = mode;
|
|
1993
|
-
} else {
|
|
1994
|
-
logger.error(`Failed to set ${mode} mode for device: ${this.deviceName}`);
|
|
1995
|
-
}
|
|
1996
|
-
return success;
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
/**
|
|
2000
|
-
* Change fan speed
|
|
2001
|
-
*/
|
|
2002
|
-
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
2003
|
-
// const speeds = this.config.levels ?? [];
|
|
2004
|
-
// if (!speeds.includes(speed)) {
|
|
2005
|
-
// logger.debug(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')}`);
|
|
2006
|
-
// return false;
|
|
2007
|
-
// }
|
|
2008
|
-
|
|
2009
|
-
const [head, body] = this.buildApiDict('setLevel');
|
|
2010
|
-
body.payload.data = {
|
|
2011
|
-
levelIdx: 0,
|
|
2012
|
-
levelType: 'wind',
|
|
2013
|
-
manualSpeedLevel: speed
|
|
2014
|
-
};
|
|
2015
|
-
|
|
2016
|
-
const [response, status] = await this.callApi(
|
|
2017
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
2018
|
-
'post',
|
|
2019
|
-
body,
|
|
2020
|
-
head
|
|
2021
|
-
);
|
|
2022
|
-
|
|
2023
|
-
const success = this.checkResponse([response, status], 'changeFanSpeed');
|
|
2024
|
-
if (success) {
|
|
2025
|
-
this.details.manualSpeedLevel = speed;
|
|
2026
|
-
} else {
|
|
2027
|
-
logger.debug('Error setting fan speed');
|
|
2028
|
-
}
|
|
2029
|
-
return success;
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
/**
|
|
2033
|
-
* Set normal mode
|
|
2034
|
-
*/
|
|
2035
|
-
async normal_mode(): Promise<boolean> {
|
|
2036
|
-
logger.debug(`Setting normal mode for device: ${this.deviceName}`);
|
|
2037
|
-
return this.mode_toggle('normal');
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
/**
|
|
2041
|
-
* Set manual mode - adapter to set mode to normal
|
|
2042
|
-
*/
|
|
2043
|
-
async manual_mode(): Promise<boolean> {
|
|
2044
|
-
logger.debug(`Setting manual mode for device: ${this.deviceName}`);
|
|
2045
|
-
return this.normal_mode();
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
/**
|
|
2049
|
-
* Set advanced sleep mode
|
|
2050
|
-
*/
|
|
2051
|
-
async advanced_sleep_mode(): Promise<boolean> {
|
|
2052
|
-
logger.debug(`Setting advanced sleep mode for device: ${this.deviceName}`);
|
|
2053
|
-
return this.mode_toggle('advancedSleep');
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
/**
|
|
2057
|
-
* Set sleep mode - adapter for advanced sleep mode
|
|
2058
|
-
*/
|
|
2059
|
-
async sleep_mode(): Promise<boolean> {
|
|
2060
|
-
logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
|
|
2061
|
-
return this.advanced_sleep_mode();
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
/**
|
|
2065
|
-
* Override setMode to handle tower fan specific modes
|
|
2066
|
-
*/
|
|
2067
|
-
async setMode(mode: string): Promise<boolean> {
|
|
2068
|
-
return this.mode_toggle(mode);
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
/**
|
|
2072
|
-
* Get current speed
|
|
2073
|
-
*/
|
|
2074
|
-
get speed(): number {
|
|
2075
|
-
return this.details.manualSpeedLevel || 0;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
/**
|
|
2079
|
-
* Get display status
|
|
2080
|
-
*/
|
|
2081
|
-
get displayState(): boolean {
|
|
2082
|
-
return Boolean(this.details.screenState);
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
/**
|
|
2086
|
-
* Get oscillation status
|
|
2087
|
-
*/
|
|
2088
|
-
get oscillationState(): boolean {
|
|
2089
|
-
return Boolean(this.details.oscillationState);
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
/**
|
|
2093
|
-
* Get mute status
|
|
2094
|
-
*/
|
|
2095
|
-
get muteState(): boolean {
|
|
2096
|
-
return Boolean(this.details.muteState);
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
/**
|
|
2100
|
-
* Get temperature (in tenths of a degree)
|
|
2101
|
-
*/
|
|
2102
|
-
get temperature(): number {
|
|
2103
|
-
return this.details.temperature || 0;
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
/**
|
|
2107
|
-
* Get humidity percentage
|
|
2108
|
-
*/
|
|
2109
|
-
get humidity(): number {
|
|
2110
|
-
return this.details.humidity || 0;
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
/**
|
|
2114
|
-
* Get thermal comfort level
|
|
2115
|
-
*/
|
|
2116
|
-
get thermalComfort(): number {
|
|
2117
|
-
return this.details.thermalComfort || 0;
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
/**
|
|
2121
|
-
* Get sleep preferences
|
|
2122
|
-
*/
|
|
2123
|
-
get sleepPreference(): Record<string, any> {
|
|
2124
|
-
return this.details.sleepPreference || {};
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
/**
|
|
2128
|
-
* Get schedule count
|
|
2129
|
-
*/
|
|
2130
|
-
get scheduleCount(): number {
|
|
2131
|
-
return this.details.scheduleCount || 0;
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
/**
|
|
2135
|
-
* Get error code
|
|
2136
|
-
*/
|
|
2137
|
-
get errorCode(): number {
|
|
2138
|
-
return this.details.errorCode || 0;
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
/**
|
|
2142
|
-
* Turn oscillation on
|
|
2143
|
-
*/
|
|
2144
|
-
async setOscillationOn(): Promise<boolean> {
|
|
2145
|
-
return this.setOscillation(true);
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
/**
|
|
2149
|
-
* Turn oscillation off
|
|
2150
|
-
*/
|
|
2151
|
-
async setOscillationOff(): Promise<boolean> {
|
|
2152
|
-
return this.setOscillation(false);
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
/**
|
|
2156
|
-
* Display device info
|
|
2157
|
-
*/
|
|
2158
|
-
override display(): void {
|
|
2159
|
-
super.display();
|
|
2160
|
-
const info = [
|
|
2161
|
-
['Mode: ', this.details.workMode || ''],
|
|
2162
|
-
['Speed: ', this.speed],
|
|
2163
|
-
['Fan Speed Level: ', this.details.fanSpeedLevel || 0],
|
|
2164
|
-
['Display: ', this.displayState],
|
|
2165
|
-
['Display Switch: ', this.details.screenSwitch],
|
|
2166
|
-
['Oscillation: ', this.oscillationState],
|
|
2167
|
-
['Oscillation Switch: ', this.details.oscillationSwitch],
|
|
2168
|
-
['Mute: ', this.muteState],
|
|
2169
|
-
['Mute Switch: ', this.details.muteSwitch],
|
|
2170
|
-
['Timer: ', this.timer],
|
|
2171
|
-
['Temperature: ', this.temperature / 10, '°C'],
|
|
2172
|
-
['Humidity: ', this.humidity, '%'],
|
|
2173
|
-
['Thermal Comfort: ', this.thermalComfort],
|
|
2174
|
-
['Sleep Preference: ', JSON.stringify(this.sleepPreference)],
|
|
2175
|
-
['Schedule Count: ', this.scheduleCount],
|
|
2176
|
-
['Displaying Type: ', this.details.displayingType],
|
|
2177
|
-
['Error Code: ', this.errorCode]
|
|
2178
|
-
];
|
|
2179
|
-
|
|
2180
|
-
for (const [key, value, unit = ''] of info) {
|
|
2181
|
-
logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
// Export fan modules dictionary
|
|
2187
|
-
export const fanModules: Record<string, new (details: Record<string, any>, manager: VeSync) => VeSyncFan> = {
|
|
2188
|
-
// Core Series
|
|
2189
|
-
'Core200S': VeSyncAirBypass,
|
|
2190
|
-
'Core300S': VeSyncAirBypass,
|
|
2191
|
-
'Core400S': VeSyncAirBypass,
|
|
2192
|
-
'Core600S': VeSyncAirBypass,
|
|
2193
|
-
|
|
2194
|
-
// LAP Series
|
|
2195
|
-
'LAP-C201S-AUSR': VeSyncAirBypass,
|
|
2196
|
-
'LAP-C202S-WUSR': VeSyncAirBypass,
|
|
2197
|
-
'LAP-C301S-WJP': VeSyncAirBypass,
|
|
2198
|
-
'LAP-C302S-WUSB': VeSyncAirBypass,
|
|
2199
|
-
'LAP-C301S-WAAA': VeSyncAirBypass,
|
|
2200
|
-
'LAP-C401S-WJP': VeSyncAirBypass,
|
|
2201
|
-
'LAP-C401S-WUSR': VeSyncAirBypass,
|
|
2202
|
-
'LAP-C401S-WAAA': VeSyncAirBypass,
|
|
2203
|
-
'LAP-C601S-WUS': VeSyncAirBypass,
|
|
2204
|
-
'LAP-C601S-WUSR': VeSyncAirBypass,
|
|
2205
|
-
'LAP-C601S-WEU': VeSyncAirBypass,
|
|
2206
|
-
// Vital100S Series - Using VeSyncAirBaseV2
|
|
2207
|
-
'LAP-V102S-AASR': VeSyncAirBaseV2,
|
|
2208
|
-
'LAP-V102S-WUS': VeSyncAirBaseV2,
|
|
2209
|
-
'LAP-V102S-WEU': VeSyncAirBaseV2,
|
|
2210
|
-
'LAP-V102S-AUSR': VeSyncAirBaseV2,
|
|
2211
|
-
'LAP-V102S-WJP': VeSyncAirBaseV2,
|
|
2212
|
-
// Vital200S Series - Using VeSyncAirBaseV2
|
|
2213
|
-
'LAP-V201S-AASR': VeSyncAirBaseV2,
|
|
2214
|
-
'LAP-V201S-WJP': VeSyncAirBaseV2,
|
|
2215
|
-
'LAP-V201S-WEU': VeSyncAirBaseV2,
|
|
2216
|
-
'LAP-V201S-WUS': VeSyncAirBaseV2,
|
|
2217
|
-
'LAP-V201-AUSR': VeSyncAirBaseV2,
|
|
2218
|
-
'LAP-V201S-AUSR': VeSyncAirBaseV2,
|
|
2219
|
-
'LAP-V201S-AEUR': VeSyncAirBaseV2,
|
|
2220
|
-
// EverestAir Series - Using VeSyncAirBaseV2
|
|
2221
|
-
'LAP-EL551S-AUS': VeSyncAirBaseV2,
|
|
2222
|
-
'LAP-EL551S-AEUR': VeSyncAirBaseV2,
|
|
2223
|
-
'LAP-EL551S-WEU': VeSyncAirBaseV2,
|
|
2224
|
-
'LAP-EL551S-WUS': VeSyncAirBaseV2,
|
|
2225
|
-
|
|
2226
|
-
// LTF Series
|
|
2227
|
-
'LTF-F422S-KEU': VeSyncTowerFan,
|
|
2228
|
-
'LTF-F422S-WUSR': VeSyncTowerFan,
|
|
2229
|
-
'LTF-F422_WJP': VeSyncTowerFan,
|
|
2230
|
-
'LTF-F422S-WUS': VeSyncTowerFan,
|
|
2231
|
-
|
|
2232
|
-
// Classic Series
|
|
2233
|
-
'Classic300S': VeSyncHumidifier,
|
|
2234
|
-
'Classic200S': VeSyncHumidifier,
|
|
2235
|
-
|
|
2236
|
-
// Dual Series
|
|
2237
|
-
'Dual200S': VeSyncHumidifier,
|
|
2238
|
-
|
|
2239
|
-
// LUH Series
|
|
2240
|
-
'LUH-A601S-WUSB': VeSyncWarmHumidifier,
|
|
2241
|
-
'LUH-A601S-AUSW': VeSyncWarmHumidifier,
|
|
2242
|
-
'LUH-D301S-WUSR': VeSyncWarmHumidifier,
|
|
2243
|
-
'LUH-D301S-WJP': VeSyncWarmHumidifier,
|
|
2244
|
-
'LUH-D301S-WEU': VeSyncWarmHumidifier,
|
|
2245
|
-
'LUH-A602S-WUSR': VeSyncWarmHumidifier,
|
|
2246
|
-
'LUH-A602S-WUS': VeSyncWarmHumidifier,
|
|
2247
|
-
'LUH-A602S-WEUR': VeSyncWarmHumidifier,
|
|
2248
|
-
'LUH-A602S-WEU': VeSyncWarmHumidifier,
|
|
2249
|
-
'LUH-A602S-WJP': VeSyncWarmHumidifier,
|
|
2250
|
-
'LUH-A602S-WUSC': VeSyncWarmHumidifier,
|
|
2251
|
-
'LUH-O451S-WEU': VeSyncWarmHumidifier,
|
|
2252
|
-
'LUH-O451S-WUS': VeSyncWarmHumidifier,
|
|
2253
|
-
'LUH-O451S-WUSR': VeSyncWarmHumidifier,
|
|
2254
|
-
'LUH-O601S-WUS': VeSyncWarmHumidifier,
|
|
2255
|
-
'LUH-O601S-KUS': VeSyncWarmHumidifier,
|
|
2256
|
-
'LUH-M101S-WUS': VeSyncWarmHumidifier,
|
|
2257
|
-
'LUH-M101S-WEUR': VeSyncWarmHumidifier,
|
|
2258
|
-
|
|
2259
|
-
// LEH Series
|
|
2260
|
-
'LEH-S601S-WUS': VeSyncWarmHumidifier,
|
|
2261
|
-
'LEH-S601S-WUSR': VeSyncWarmHumidifier,
|
|
2262
|
-
|
|
2263
|
-
// LV Series
|
|
2264
|
-
'LV-PUR131S': VeSyncAirBypass,
|
|
2265
|
-
'LV-RH131S': VeSyncAirBypass
|
|
2266
|
-
};
|
|
5
|
+
// Re-export all fan implementations from the fans directory
|
|
6
|
+
export * from './fans';
|