tsvesync 1.0.63 → 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.
@@ -2,2263 +2,5 @@
2
2
  * VeSync Fan Implementations
3
3
  */
4
4
 
5
- import { VeSyncFan } from './vesyncFan';
6
- import { VeSync } from './vesync';
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: 'setHumidifierMode',
807
- source: 'APP'
808
- }
809
- },
810
- Helpers.reqHeaderBypass()
811
- );
812
-
813
- const success = this.checkResponse([response, status], 'setMode');
814
- if (!success) {
815
- logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
816
- }
817
- return success;
818
- }
819
-
820
- /**
821
- * Set target humidity
822
- */
823
- async setHumidity(humidity: number): Promise<boolean> {
824
- if (!this.hasFeature('humidity')) {
825
- const error = 'Humidity control not supported';
826
- logger.error(`${error} for device: ${this.deviceName}`);
827
- throw new Error(error);
828
- }
829
-
830
- if (humidity < this.humidityRange.min || humidity > this.humidityRange.max) {
831
- const error = `Invalid humidity: ${humidity}. Must be between ${this.humidityRange.min} and ${this.humidityRange.max}`;
832
- logger.error(`${error} for device: ${this.deviceName}`);
833
- throw new Error(error);
834
- }
835
-
836
- logger.debug(`Setting target humidity to ${humidity}% for device: ${this.deviceName}`);
837
- const [response, status] = await this.callApi(
838
- '/cloud/v2/deviceManaged/bypassV2',
839
- 'post',
840
- {
841
- ...Helpers.reqBody(this.manager, 'bypassV2'),
842
- cid: this.cid,
843
- configModule: this.configModule,
844
- payload: {
845
- data: {
846
- targetHumidity: humidity
847
- },
848
- method: 'setTargetHumidity',
849
- source: 'APP'
850
- }
851
- },
852
- Helpers.reqHeaderBypass()
853
- );
854
-
855
- const success = this.checkResponse([response, status], 'setHumidity');
856
- if (!success) {
857
- logger.error(`Failed to set target humidity to ${humidity}% for device: ${this.deviceName}`);
858
- }
859
- return success;
860
- }
861
-
862
- /**
863
- * Set display status
864
- */
865
- async setDisplay(enabled: boolean): Promise<boolean> {
866
- if (!this.hasFeature('display')) {
867
- const error = 'Display control not supported';
868
- logger.error(`${error} for device: ${this.deviceName}`);
869
- throw new Error(error);
870
- }
871
-
872
- logger.debug(`Setting display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
873
- const [response, status] = await this.callApi(
874
- '/cloud/v2/deviceManaged/bypassV2',
875
- 'post',
876
- {
877
- ...Helpers.reqBody(this.manager, 'bypassV2'),
878
- cid: this.cid,
879
- configModule: this.configModule,
880
- payload: {
881
- data: {
882
- state: enabled
883
- },
884
- method: 'setDisplay',
885
- source: 'APP'
886
- }
887
- },
888
- Helpers.reqHeaderBypass()
889
- );
890
-
891
- const success = this.checkResponse([response, status], 'setDisplay');
892
- if (!success) {
893
- logger.error(`Failed to set display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
894
- }
895
- return success;
896
- }
897
-
898
- /**
899
- * Set timer
900
- */
901
- async setTimer(hours: number): Promise<boolean> {
902
- if (!this.hasFeature('timer')) {
903
- const error = 'Timer not supported';
904
- logger.error(`${error} for device: ${this.deviceName}`);
905
- throw new Error(error);
906
- }
907
-
908
- logger.debug(`Setting timer to ${hours} hours for device: ${this.deviceName}`);
909
- const [response, status] = await this.callApi(
910
- '/cloud/v2/deviceManaged/bypassV2',
911
- 'post',
912
- {
913
- ...Helpers.reqBody(this.manager, 'bypassV2'),
914
- cid: this.cid,
915
- configModule: this.configModule,
916
- payload: {
917
- data: {
918
- action: 'off',
919
- total: hours * 3600
920
- },
921
- method: 'addTimer',
922
- source: 'APP'
923
- }
924
- },
925
- Helpers.reqHeaderBypass()
926
- );
927
-
928
- const success = this.checkResponse([response, status], 'setTimer');
929
- if (!success) {
930
- logger.error(`Failed to set timer to ${hours} hours for device: ${this.deviceName}`);
931
- }
932
- return success;
933
- }
934
-
935
- /**
936
- * Clear timer
937
- */
938
- async clearTimer(): Promise<boolean> {
939
- if (!this.hasFeature('timer')) {
940
- const error = 'Timer not supported';
941
- logger.error(`${error} for device: ${this.deviceName}`);
942
- throw new Error(error);
943
- }
944
-
945
- logger.debug(`Clearing timer for device: ${this.deviceName}`);
946
- const [response, status] = await this.callApi(
947
- '/cloud/v2/deviceManaged/bypassV2',
948
- 'post',
949
- {
950
- ...Helpers.reqBody(this.manager, 'bypassV2'),
951
- cid: this.cid,
952
- configModule: this.configModule,
953
- payload: {
954
- data: {},
955
- method: 'deleteTimer',
956
- source: 'APP'
957
- }
958
- },
959
- Helpers.reqHeaderBypass()
960
- );
961
-
962
- const success = this.checkResponse([response, status], 'clearTimer');
963
- if (!success) {
964
- logger.error(`Failed to clear timer for device: ${this.deviceName}`);
965
- }
966
- return success;
967
- }
968
-
969
- /**
970
- * Turn automatic stop on
971
- */
972
- async automaticStopOn(): Promise<boolean> {
973
- logger.debug(`Setting automatic stop on for device: ${this.deviceName}`);
974
- const [response, status] = await this.callApi(
975
- '/cloud/v2/deviceManaged/bypassV2',
976
- 'post',
977
- {
978
- ...Helpers.reqBody(this.manager, 'bypassV2'),
979
- cid: this.cid,
980
- configModule: this.configModule,
981
- payload: {
982
- data: {
983
- enabled: true
984
- },
985
- method: 'setAutomaticStop',
986
- source: 'APP'
987
- }
988
- },
989
- Helpers.reqHeaderBypass()
990
- );
991
-
992
- const success = this.checkResponse([response, status], 'automaticStopOn');
993
- if (!success) {
994
- logger.error(`Failed to set automatic stop on for device: ${this.deviceName}`);
995
- }
996
- return success;
997
- }
998
-
999
- /**
1000
- * Turn automatic stop off
1001
- */
1002
- async automaticStopOff(): Promise<boolean> {
1003
- logger.debug(`Setting automatic stop off for device: ${this.deviceName}`);
1004
- const [response, status] = await this.callApi(
1005
- '/cloud/v2/deviceManaged/bypassV2',
1006
- 'post',
1007
- {
1008
- ...Helpers.reqBody(this.manager, 'bypassV2'),
1009
- cid: this.cid,
1010
- configModule: this.configModule,
1011
- payload: {
1012
- data: {
1013
- enabled: false
1014
- },
1015
- method: 'setAutomaticStop',
1016
- source: 'APP'
1017
- }
1018
- },
1019
- Helpers.reqHeaderBypass()
1020
- );
1021
-
1022
- const success = this.checkResponse([response, status], 'automaticStopOff');
1023
- if (!success) {
1024
- logger.error(`Failed to set automatic stop off for device: ${this.deviceName}`);
1025
- }
1026
- return success;
1027
- }
1028
-
1029
- /**
1030
- * Set auto mode
1031
- */
1032
- async setAutoMode(): Promise<boolean> {
1033
- logger.debug(`Setting auto mode for device: ${this.deviceName}`);
1034
- const success = await this.setMode('auto');
1035
- if (!success) {
1036
- logger.error(`Failed to set auto mode for device: ${this.deviceName}`);
1037
- }
1038
- return success;
1039
- }
1040
-
1041
- /**
1042
- * Set manual mode
1043
- */
1044
- async setManualMode(): Promise<boolean> {
1045
- logger.debug(`Setting manual mode for device: ${this.deviceName}`);
1046
- const success = await this.setMode('manual');
1047
- if (!success) {
1048
- logger.error(`Failed to set manual mode for device: ${this.deviceName}`);
1049
- }
1050
- return success;
1051
- }
1052
-
1053
- /**
1054
- * Turn off display
1055
- */
1056
- async turnOffDisplay(): Promise<boolean> {
1057
- logger.debug(`Turning off display for device: ${this.deviceName}`);
1058
- const success = await this.setDisplay(false);
1059
- if (!success) {
1060
- logger.error(`Failed to turn off display for device: ${this.deviceName}`);
1061
- }
1062
- return success;
1063
- }
1064
-
1065
- /**
1066
- * Turn on display
1067
- */
1068
- async turnOnDisplay(): Promise<boolean> {
1069
- logger.debug(`Turning on display for device: ${this.deviceName}`);
1070
- const success = await this.setDisplay(true);
1071
- if (!success) {
1072
- logger.error(`Failed to turn on display for device: ${this.deviceName}`);
1073
- }
1074
- return success;
1075
- }
1076
- }
1077
-
1078
- /**
1079
- * VeSync Warm Humidifier Class
1080
- */
1081
- export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1082
- protected readonly warmLevels = [1, 2, 3];
1083
-
1084
- constructor(details: Record<string, any>, manager: VeSync) {
1085
- super(details, manager);
1086
- logger.debug(`Initialized VeSyncWarmHumidifier device: ${this.deviceName}`);
1087
- }
1088
-
1089
- /**
1090
- * Get device details
1091
- */
1092
- async getDetails(): Promise<Boolean> {
1093
- const success = await super.getDetails();
1094
- if (success && this.details) {
1095
- // Add warm humidifier specific fields
1096
- this.details = {
1097
- ...this.details,
1098
- warm_enabled: Boolean(this.details.warm_level && this.details.warm_level > 0),
1099
- drying_mode_enabled: this.details.autoDryingSwitch === 1,
1100
- drying_mode_state: this.details.dryingMode || '',
1101
- drying_mode_level: this.details.dryingLevel || 0,
1102
- drying_mode_seconds_remaining: this.details.dryingRemainTime || 0
1103
- };
1104
- }
1105
- return success;
1106
- }
1107
-
1108
- /**
1109
- * Change fan speed - Implemented to satisfy interface but redirects to setMistLevel
1110
- */
1111
- async changeFanSpeed(speed: number): Promise<boolean> {
1112
- logger.debug(`Redirecting fan speed change to mist level for device: ${this.deviceName}`);
1113
- return this.setMistLevel(speed);
1114
- }
1115
-
1116
- /**
1117
- * Set device mode
1118
- */
1119
- async setMode(mode: string): Promise<boolean> {
1120
- if (!this.modes.includes(mode as any)) {
1121
- const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
1122
- logger.error(`${error} for device: ${this.deviceName}`);
1123
- throw new Error(error);
1124
- }
1125
-
1126
- logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
1127
- const [response, status] = await this.callApi(
1128
- '/cloud/v2/deviceManaged/bypassV2',
1129
- 'post',
1130
- {
1131
- ...Helpers.reqBody(this.manager, 'bypassV2'),
1132
- cid: this.cid,
1133
- configModule: this.configModule,
1134
- payload: {
1135
- data: {
1136
- mode
1137
- },
1138
- method: 'setHumidifierMode',
1139
- source: 'APP'
1140
- }
1141
- },
1142
- Helpers.reqHeaderBypass()
1143
- );
1144
-
1145
- const success = this.checkResponse([response, status], 'setMode');
1146
- if (success) {
1147
- this.details.mode = mode;
1148
- } else {
1149
- logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
1150
- }
1151
- return success;
1152
- }
1153
-
1154
- /**
1155
- * Set warm mist level
1156
- */
1157
- async setWarmLevel(level: number): Promise<boolean> {
1158
- if (!this.hasFeature('warm')) {
1159
- const error = 'Warm mist control not supported';
1160
- logger.error(`${error} for device: ${this.deviceName}`);
1161
- throw new Error(error);
1162
- }
1163
-
1164
- if (!this.warmLevels.includes(level)) {
1165
- const error = `Invalid warm level: ${level}. Must be one of: ${this.warmLevels.join(', ')}`;
1166
- logger.error(`${error} for device: ${this.deviceName}`);
1167
- throw new Error(error);
1168
- }
1169
-
1170
- logger.debug(`Setting warm level to ${level} for device: ${this.deviceName}`);
1171
- const [response, status] = await this.callApi(
1172
- '/cloud/v2/deviceManaged/bypassV2',
1173
- 'post',
1174
- {
1175
- ...Helpers.reqBody(this.manager, 'bypassV2'),
1176
- cid: this.cid,
1177
- configModule: this.configModule,
1178
- payload: {
1179
- data: {
1180
- warmLevel: level
1181
- },
1182
- method: 'setWarmLevel',
1183
- source: 'APP'
1184
- }
1185
- },
1186
- Helpers.reqHeaderBypass()
1187
- );
1188
-
1189
- const success = this.checkResponse([response, status], 'setWarmLevel');
1190
- if (success) {
1191
- this.details.warm_level = level;
1192
- this.details.warm_enabled = level > 0;
1193
- } else {
1194
- logger.error(`Failed to set warm level to ${level} for device: ${this.deviceName}`);
1195
- }
1196
- return success;
1197
- }
1198
-
1199
- /**
1200
- * Set drying mode enabled
1201
- */
1202
- async setDryingModeEnabled(enabled: boolean): Promise<boolean> {
1203
- if (!this.hasFeature('drying')) {
1204
- const error = 'Drying mode not supported';
1205
- logger.error(`${error} for device: ${this.deviceName}`);
1206
- throw new Error(error);
1207
- }
1208
-
1209
- logger.debug(`Setting drying mode to ${enabled} for device: ${this.deviceName}`);
1210
- const [response, status] = await this.callApi(
1211
- '/cloud/v2/deviceManaged/bypassV2',
1212
- 'post',
1213
- {
1214
- ...Helpers.reqBody(this.manager, 'bypassV2'),
1215
- cid: this.cid,
1216
- configModule: this.configModule,
1217
- payload: {
1218
- data: {
1219
- autoDryingSwitch: enabled ? 1 : 0
1220
- },
1221
- method: 'setDryingMode',
1222
- source: 'APP'
1223
- }
1224
- },
1225
- Helpers.reqHeaderBypass()
1226
- );
1227
-
1228
- const success = this.checkResponse([response, status], 'setDryingModeEnabled');
1229
- if (success) {
1230
- this.details.drying_mode_enabled = enabled;
1231
- this.details.autoDryingSwitch = enabled ? 1 : 0;
1232
- } else {
1233
- logger.error(`Failed to set drying mode to ${enabled} for device: ${this.deviceName}`);
1234
- }
1235
- return success;
1236
- }
1237
-
1238
- /**
1239
- * Get warm mist enabled status
1240
- */
1241
- get warmMistEnabled(): boolean {
1242
- return this.details.warm_enabled || false;
1243
- }
1244
-
1245
- /**
1246
- * Get drying mode enabled status
1247
- */
1248
- get dryingModeEnabled(): boolean {
1249
- return this.details.drying_mode_enabled || false;
1250
- }
1251
-
1252
- /**
1253
- * Get drying mode state
1254
- */
1255
- get dryingModeState(): string {
1256
- return this.details.drying_mode_state || '';
1257
- }
1258
-
1259
- /**
1260
- * Get drying mode level
1261
- */
1262
- get dryingModeLevel(): number {
1263
- return this.details.drying_mode_level || 0;
1264
- }
1265
-
1266
- /**
1267
- * Get drying mode seconds remaining
1268
- */
1269
- get dryingModeSecondsRemaining(): number {
1270
- return this.details.drying_mode_seconds_remaining || 0;
1271
- }
1272
- }
1273
-
1274
- /**
1275
- * VeSync Air Purifier with Bypass V2
1276
- */
1277
- export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1278
- protected _lightDetection: boolean = false;
1279
- protected _lightDetectionState: boolean = false;
1280
- protected setSpeedLevel: number | null = null;
1281
- protected autoPreferences: string[] = ['default', 'efficient', 'quiet'];
1282
- protected enabled: boolean = false;
1283
- protected _mode: string = '';
1284
- protected _speed: number = 0;
1285
- protected _timer: { duration: number; action: string } | null = null;
1286
-
1287
- constructor(details: Record<string, any>, manager: VeSync) {
1288
- super(details, manager);
1289
- logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
1290
- }
1291
-
1292
- protected buildConfigDict(configDict: Record<string, any>): void {
1293
- if (configDict) {
1294
- this.config = {
1295
- ...this.config, // Preserve existing config
1296
- ...configDict // Merge in new config
1297
- };
1298
- }
1299
- }
1300
-
1301
- get mode(): string {
1302
- return this._mode;
1303
- }
1304
-
1305
- set mode(value: string) {
1306
- this._mode = value;
1307
- }
1308
-
1309
- get speed(): number {
1310
- return this._speed;
1311
- }
1312
-
1313
- set speed(value: number) {
1314
- this._speed = value;
1315
- }
1316
-
1317
- get timer(): { duration: number; action: string } | null {
1318
- return this._timer;
1319
- }
1320
-
1321
- set timer(value: { duration: number; action: string } | null) {
1322
- this._timer = value;
1323
- }
1324
-
1325
- /**
1326
- * Build API dictionary
1327
- */
1328
- protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
1329
- const head = Helpers.reqHeaderBypass();
1330
- const body = {
1331
- ...Helpers.reqBody(this.manager, 'bypassV2'),
1332
- cid: this.cid,
1333
- configModule: this.configModule,
1334
- payload: {
1335
- method,
1336
- source: 'APP',
1337
- data: {}
1338
- }
1339
- };
1340
- return [head, body];
1341
- }
1342
-
1343
- /**
1344
- * Get device details
1345
- */
1346
- async getDetails(): Promise<Boolean> {
1347
- logger.debug(`Getting details for device: ${this.deviceName}`);
1348
- const [head, body] = this.buildApiDict('getPurifierStatus');
1349
-
1350
- const [response, status] = await this.callApi(
1351
- '/cloud/v2/deviceManaged/bypassV2',
1352
- 'post',
1353
- body,
1354
- head
1355
- );
1356
-
1357
- if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
1358
- logger.debug('Error getting purifier details');
1359
- this.connectionStatus = 'offline';
1360
- return false;
1361
- }
1362
-
1363
- const innerResponse = response.result;
1364
- if (innerResponse.code !== 0 || !innerResponse.result) {
1365
- logger.debug('Error in inner response from purifier');
1366
- this.connectionStatus = 'offline';
1367
- return false;
1368
- }
1369
-
1370
- const deviceData = innerResponse.result;
1371
- this.buildPurifierDict(deviceData);
1372
- if (deviceData.configuration) {
1373
- this.buildConfigDict(deviceData.configuration);
1374
- }
1375
- return true;
1376
- }
1377
-
1378
- /**
1379
- * Build purifier dictionary
1380
- */
1381
- protected buildPurifierDict(devDict: Record<string, any>): void {
1382
- this.connectionStatus = 'online';
1383
- const powerSwitch = Boolean(devDict.powerSwitch ?? 0);
1384
- this.enabled = powerSwitch;
1385
- this.deviceStatus = powerSwitch ? 'on' : 'off';
1386
- this.mode = devDict.workMode ?? 'manual';
1387
-
1388
- this.speed = devDict.fanSpeedLevel ?? 0;
1389
- this.setSpeedLevel = devDict.manualSpeedLevel ?? 1;
1390
-
1391
- this.details = {
1392
- ...this.details,
1393
- filter_life: devDict.filterLifePercent ?? 0,
1394
- child_lock: Boolean(devDict.childLockSwitch ?? 0),
1395
- display: Boolean(devDict.screenState ?? 0),
1396
- light_detection_switch: Boolean(devDict.lightDetectionSwitch ?? 0),
1397
- environment_light_state: Boolean(devDict.environmentLightState ?? 0),
1398
- screen_switch: Boolean(devDict.screenSwitch ?? 0)
1399
- };
1400
-
1401
- if (this.hasFeature('air_quality')) {
1402
- this.details.air_quality_value = devDict.PM25 ?? 0;
1403
- this.details.air_quality = devDict.AQLevel ?? 0;
1404
- }
1405
-
1406
- if ('PM1' in devDict) this.details.pm1 = devDict.PM1;
1407
- if ('PM10' in devDict) this.details.pm10 = devDict.PM10;
1408
- if ('AQPercent' in devDict) this.details.aq_percent = devDict.AQPercent;
1409
- if ('fanRotateAngle' in devDict) this.details.fan_rotate_angle = devDict.fanRotateAngle;
1410
- if ('filterOpenState' in devDict) this.details.filter_open_state = Boolean(devDict.filterOpenState);
1411
-
1412
- if ((devDict.timerRemain ?? 0) > 0) {
1413
- this.timer = { duration: devDict.timerRemain, action: 'off' };
1414
- }
1415
-
1416
- if (typeof devDict.autoPreference === 'object' && devDict.autoPreference) {
1417
- this.details.auto_preference_type = devDict.autoPreference?.autoPreferenceType ?? 'default';
1418
- } else {
1419
- this.details.auto_preference_type = null;
1420
- }
1421
- }
1422
-
1423
- /**
1424
- * Toggle device power
1425
- */
1426
- async toggleSwitch(toggle: boolean): Promise<boolean> {
1427
- if (typeof toggle !== 'boolean') {
1428
- logger.debug('Invalid toggle value for purifier switch');
1429
- return false;
1430
- }
1431
-
1432
- const [head, body] = this.buildApiDict('setSwitch');
1433
- body.payload.data = {
1434
- powerSwitch: toggle ? 1 : 0,
1435
- switchIdx: 0
1436
- };
1437
-
1438
- const [response, status] = await this.callApi(
1439
- '/cloud/v2/deviceManaged/bypassV2',
1440
- 'post',
1441
- body,
1442
- head
1443
- );
1444
-
1445
- const success = this.checkResponse([response, status], 'toggleSwitch');
1446
- if (success) {
1447
- this.deviceStatus = toggle ? 'on' : 'off';
1448
- }
1449
- return success;
1450
- }
1451
-
1452
- /**
1453
- * Turn device on - compatibility method that proxies to toggleSwitch
1454
- */
1455
- async turnOn(): Promise<boolean> {
1456
- logger.info(`Turning on device: ${this.deviceName}`);
1457
- return this.toggleSwitch(true);
1458
- }
1459
-
1460
- /**
1461
- * Turn device off - compatibility method that proxies to toggleSwitch
1462
- */
1463
- async turnOff(): Promise<boolean> {
1464
- logger.info(`Turning off device: ${this.deviceName}`);
1465
- return this.toggleSwitch(false);
1466
- }
1467
-
1468
- /**
1469
- * Change fan speed
1470
- */
1471
- async changeFanSpeed(speed: number): Promise<boolean> {
1472
- // const speeds = this.config.levels ?? [];
1473
- // if (!speeds.includes(speed)) {
1474
- // logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
1475
- // return false;
1476
- // }
1477
-
1478
- logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
1479
- const [response, status] = await this.callApi(
1480
- '/cloud/v2/deviceManaged/bypassV2',
1481
- 'post',
1482
- {
1483
- ...Helpers.reqBody(this.manager, 'bypassV2'),
1484
- cid: this.cid,
1485
- configModule: this.configModule,
1486
- payload: {
1487
- data: {
1488
- id: 0,
1489
- level: speed,
1490
- mode: 'manual',
1491
- type: 'wind'
1492
- },
1493
- method: 'setLevel',
1494
- source: 'APP'
1495
- }
1496
- },
1497
- Helpers.reqHeaderBypass()
1498
- );
1499
-
1500
- const success = this.checkResponse([response, status], 'changeFanSpeed');
1501
- if (success) {
1502
- this.setSpeedLevel = speed;
1503
- this.mode = 'manual';
1504
- }
1505
- return success;
1506
- }
1507
-
1508
- /**
1509
- * Set mode
1510
- */
1511
- async setMode(mode: string): Promise<boolean> {
1512
- if (!this.modes.includes(mode as any)) {
1513
- logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`);
1514
- return false;
1515
- }
1516
-
1517
- if (mode === 'manual') {
1518
- if (!this.speed || this.speed === 0) {
1519
- return this.changeFanSpeed(1);
1520
- }
1521
- return this.changeFanSpeed(this.speed);
1522
- }
1523
-
1524
- if (mode === 'off') {
1525
- return this.turnOff();
1526
- }
1527
-
1528
- const [head, body] = this.buildApiDict('setPurifierMode');
1529
- body.payload.data = {
1530
- workMode: mode
1531
- };
1532
-
1533
- const [response, status] = await this.callApi(
1534
- '/cloud/v2/deviceManaged/bypassV2',
1535
- 'post',
1536
- body,
1537
- head
1538
- );
1539
-
1540
- const success = this.checkResponse([response, status], 'setMode');
1541
- if (success) {
1542
- this.mode = mode;
1543
- }
1544
- return success;
1545
- }
1546
-
1547
- /**
1548
- * Set display status
1549
- */
1550
- async setDisplay(enabled: boolean): Promise<boolean> {
1551
- if (!this.hasFeature('display')) {
1552
- const error = 'Display control not supported';
1553
- logger.error(`${error} for device: ${this.deviceName}`);
1554
- throw new Error(error);
1555
- }
1556
-
1557
- const [head, body] = this.buildApiDict('setDisplay');
1558
- body.payload.data = {
1559
- screenSwitch: enabled ? 1 : 0
1560
- };
1561
-
1562
- const [response, status] = await this.callApi(
1563
- '/cloud/v2/deviceManaged/bypassV2',
1564
- 'post',
1565
- body,
1566
- head
1567
- );
1568
-
1569
- const success = this.checkResponse([response, status], 'setDisplay');
1570
- if (success) {
1571
- this.details.screen_switch = enabled;
1572
- }
1573
- return success;
1574
- }
1575
-
1576
- /**
1577
- * Set child lock
1578
- */
1579
- async setChildLock(enabled: boolean): Promise<boolean> {
1580
- if (!this.hasFeature('child_lock')) {
1581
- const error = 'Child lock not supported';
1582
- logger.error(`${error} for device: ${this.deviceName}`);
1583
- throw new Error(error);
1584
- }
1585
-
1586
- const [head, body] = this.buildApiDict('setChildLock');
1587
- body.payload.data = {
1588
- childLockSwitch: enabled ? 1 : 0
1589
- };
1590
-
1591
- const [response, status] = await this.callApi(
1592
- '/cloud/v2/deviceManaged/bypassV2',
1593
- 'post',
1594
- body,
1595
- head
1596
- );
1597
-
1598
- const success = this.checkResponse([response, status], 'setChildLock');
1599
- if (success) {
1600
- this.details.child_lock = enabled;
1601
- }
1602
- return success;
1603
- }
1604
-
1605
- /**
1606
- * Set timer
1607
- */
1608
- async setTimer(timerDuration: number, action: 'on' | 'off' = 'off', method: string = 'powerSwitch'): Promise<boolean> {
1609
- if (!this.hasFeature('timer')) {
1610
- const error = 'Timer not supported';
1611
- logger.error(`${error} for device: ${this.deviceName}`);
1612
- throw new Error(error);
1613
- }
1614
-
1615
- if (!['on', 'off'].includes(action)) {
1616
- logger.debug('Invalid action for timer');
1617
- return false;
1618
- }
1619
- if (method !== 'powerSwitch') {
1620
- logger.debug('Invalid method for timer');
1621
- return false;
1622
- }
1623
-
1624
- const [head, body] = this.buildApiDict('addTimerV2');
1625
- body.payload.subDeviceNo = 0;
1626
- body.payload.data = {
1627
- startAct: [{
1628
- type: method,
1629
- num: 0,
1630
- act: action === 'on' ? 1 : 0
1631
- }],
1632
- total: timerDuration,
1633
- subDeviceNo: 0
1634
- };
1635
-
1636
- const [response, status] = await this.callApi(
1637
- '/cloud/v2/deviceManaged/bypassV2',
1638
- 'post',
1639
- body,
1640
- head
1641
- );
1642
-
1643
- const success = this.checkResponse([response, status], 'setTimer');
1644
- if (success) {
1645
- this.timer = { duration: timerDuration, action };
1646
- }
1647
- return success;
1648
- }
1649
-
1650
- /**
1651
- * Clear timer
1652
- */
1653
- async clearTimer(): Promise<boolean> {
1654
- if (!this.hasFeature('timer')) {
1655
- const error = 'Timer not supported';
1656
- logger.error(`${error} for device: ${this.deviceName}`);
1657
- throw new Error(error);
1658
- }
1659
-
1660
- const [head, body] = this.buildApiDict('delTimerV2');
1661
- body.payload.subDeviceNo = 0;
1662
- body.payload.data = { id: 1, subDeviceNo: 0 };
1663
-
1664
- const [response, status] = await this.callApi(
1665
- '/cloud/v2/deviceManaged/bypassV2',
1666
- 'post',
1667
- body,
1668
- head
1669
- );
1670
-
1671
- const success = this.checkResponse([response, status], 'clearTimer');
1672
- if (success) {
1673
- this.timer = null;
1674
- }
1675
- return success;
1676
- }
1677
-
1678
- /**
1679
- * Set auto preference
1680
- */
1681
- async setAutoPreference(preference: string = 'default', roomSize: number = 600): Promise<boolean> {
1682
- if (!this.autoPreferences.includes(preference)) {
1683
- logger.debug(`Invalid preference: ${preference} - valid preferences are default, efficient, quiet`);
1684
- return false;
1685
- }
1686
-
1687
- const [head, body] = this.buildApiDict('setAutoPreference');
1688
- body.payload.data = {
1689
- autoPreference: preference,
1690
- roomSize: roomSize
1691
- };
1692
-
1693
- const [response, status] = await this.callApi(
1694
- '/cloud/v2/deviceManaged/bypassV2',
1695
- 'post',
1696
- body,
1697
- head
1698
- );
1699
-
1700
- const success = this.checkResponse([response, status], 'setAutoPreference');
1701
- if (success) {
1702
- this.details.auto_preference = preference;
1703
- }
1704
- return success;
1705
- }
1706
-
1707
- /**
1708
- * Set light detection
1709
- */
1710
- async setLightDetection(enabled: boolean): Promise<boolean> {
1711
- const [head, body] = this.buildApiDict('setLightDetection');
1712
- body.payload.data = {
1713
- lightDetectionSwitch: enabled ? 1 : 0
1714
- };
1715
-
1716
- const [response, status] = await this.callApi(
1717
- '/cloud/v2/deviceManaged/bypassV2',
1718
- 'post',
1719
- body,
1720
- head
1721
- );
1722
-
1723
- const success = this.checkResponse([response, status], 'setLightDetection');
1724
- if (success) {
1725
- this.details.light_detection_switch = enabled;
1726
- }
1727
- return success;
1728
- }
1729
-
1730
- /**
1731
- * Get light detection status
1732
- */
1733
- get lightDetection(): boolean {
1734
- return Boolean(this.details.light_detection_switch);
1735
- }
1736
-
1737
- /**
1738
- * Get light detection state
1739
- */
1740
- get lightDetectionState(): boolean {
1741
- return Boolean(this.details.environment_light_state);
1742
- }
1743
-
1744
- /**
1745
- * Get auto preference type
1746
- */
1747
- get autoPreferenceType(): string | null {
1748
- return this.details.auto_preference_type;
1749
- }
1750
-
1751
- /**
1752
- * Display JSON details
1753
- */
1754
- override displayJSON(): string {
1755
- const baseInfo = JSON.parse(super.displayJSON());
1756
- const details: Record<string, string> = {
1757
- ...baseInfo,
1758
- 'Mode': this.mode,
1759
- 'Filter Life': (this.details.filter_life ?? 0).toString(),
1760
- 'Fan Level': this.speed.toString(),
1761
- 'Display On': this.details.display?.toString() ?? 'false',
1762
- 'Child Lock': (this.details.child_lock ?? false).toString(),
1763
- 'Night Light': (this.details.night_light ?? '').toString(),
1764
- 'Display Set On': (this.details.screen_switch ?? false).toString(),
1765
- 'Light Detection Enabled': (this.details.light_detection_switch ?? false).toString(),
1766
- 'Environment Light State': (this.details.environment_light_state ?? false).toString()
1767
- };
1768
-
1769
- if (this.hasFeature('air_quality')) {
1770
- details['Air Quality Level'] = (this.details.air_quality ?? '').toString();
1771
- details['Air Quality Value'] = (this.details.air_quality_value ?? '').toString();
1772
- }
1773
-
1774
- const everestKeys: Record<string, string> = {
1775
- 'pm1': 'PM1',
1776
- 'pm10': 'PM10',
1777
- 'fan_rotate_angle': 'Fan Rotate Angle',
1778
- 'filter_open_state': 'Filter Open State'
1779
- };
1780
-
1781
- for (const [key, value] of Object.entries(everestKeys)) {
1782
- if (key in this.details) {
1783
- details[value] = this.details[key]?.toString() ?? '';
1784
- }
1785
- }
1786
-
1787
- return JSON.stringify(details, null, 4);
1788
- }
1789
-
1790
- /**
1791
- * Set oscillation state
1792
- */
1793
- async setOscillation(toggle: boolean): Promise<boolean> {
1794
- logger.debug(`Setting oscillation to ${toggle ? 'on' : 'off'} for device: ${this.deviceName}`);
1795
-
1796
- const [head, body] = this.buildApiDict('setSwitch');
1797
- body.payload.data = {
1798
- oscillationSwitch: toggle ? 1 : 0,
1799
- switchIdx: 0
1800
- };
1801
-
1802
- const [response, status] = await this.callApi(
1803
- '/cloud/v2/deviceManaged/bypassV2',
1804
- 'post',
1805
- body,
1806
- head
1807
- );
1808
-
1809
- const success = this.checkResponse([response, status], 'setOscillation');
1810
- if (success) {
1811
- this.details.oscillationState = toggle;
1812
- this.details.oscillationSwitch = toggle ? 1 : 0;
1813
- }
1814
- return success;
1815
- }
1816
- }
1817
-
1818
- /**
1819
- * VeSync Tower Fan Implementation
1820
- */
1821
- export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1822
- protected readonly towerModes = ['normal', 'advancedSleep', 'off'] as const;
1823
-
1824
- constructor(details: Record<string, any>, manager: VeSync) {
1825
- super(details, manager);
1826
- logger.debug(`Initialized VeSyncTowerFan device: ${this.deviceName}`);
1827
- }
1828
-
1829
- /**
1830
- * Get timer status (in seconds)
1831
- */
1832
- override get timer(): { duration: number; action: string } | null {
1833
- const timerRemain = this.details.timerRemain || 0;
1834
- return timerRemain > 0 ? { duration: timerRemain, action: 'off' } : null;
1835
- }
1836
-
1837
- override set timer(value: { duration: number; action: string } | null) {
1838
- if (value) {
1839
- this.details.timerRemain = value.duration;
1840
- } else {
1841
- this.details.timerRemain = 0;
1842
- }
1843
- }
1844
-
1845
- /**
1846
- * Build API dictionary
1847
- */
1848
- protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
1849
- const head = Helpers.reqHeaderBypass();
1850
- const body = {
1851
- ...Helpers.reqBody(this.manager, 'bypassV2'),
1852
- cid: this.cid,
1853
- configModule: this.configModule,
1854
- payload: {
1855
- method,
1856
- source: 'APP',
1857
- data: {}
1858
- }
1859
- };
1860
- return [head, body];
1861
- }
1862
-
1863
- /**
1864
- * Get device details
1865
- */
1866
- async getDetails(): Promise<Boolean> {
1867
- logger.debug(`Getting details for device: ${this.deviceName}`);
1868
- const [head, body] = this.buildApiDict('getTowerFanStatus');
1869
-
1870
- const [response, status] = await this.callApi(
1871
- '/cloud/v2/deviceManaged/bypassV2',
1872
- 'post',
1873
- body,
1874
- head
1875
- );
1876
-
1877
- if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
1878
- logger.debug('Error getting tower fan details');
1879
- this.connectionStatus = 'offline';
1880
- return false;
1881
- }
1882
-
1883
- const innerResponse = response.result;
1884
- if (innerResponse.code !== 0 || !innerResponse.result) {
1885
- logger.debug('Error in inner response from tower fan');
1886
- this.connectionStatus = 'offline';
1887
- return false;
1888
- }
1889
-
1890
- const deviceData = innerResponse.result;
1891
- this.deviceStatus = deviceData.powerSwitch === 1 ? 'on' : 'off';
1892
- this.details = {
1893
- powerSwitch: deviceData.powerSwitch,
1894
- workMode: deviceData.workMode,
1895
- manualSpeedLevel: deviceData.manualSpeedLevel,
1896
- fanSpeedLevel: deviceData.fanSpeedLevel,
1897
- screenState: deviceData.screenState,
1898
- screenSwitch: deviceData.screenSwitch,
1899
- oscillationState: deviceData.oscillationState,
1900
- oscillationSwitch: deviceData.oscillationSwitch,
1901
- timerRemain: deviceData.timerRemain,
1902
- temperature: deviceData.temperature || 0,
1903
- humidity: deviceData.humidity || 0,
1904
- thermalComfort: deviceData.thermalComfort,
1905
- sleepPreference: deviceData.sleepPreference || {},
1906
- scheduleCount: deviceData.scheduleCount,
1907
- displayingType: deviceData.displayingType,
1908
- errorCode: deviceData.errorCode
1909
- };
1910
-
1911
- logger.debug(`Successfully got details for device: ${this.deviceName}`);
1912
- return true;
1913
- }
1914
-
1915
- /**
1916
- * Toggle device power
1917
- */
1918
- async toggleSwitch(toggle: boolean): Promise<boolean> {
1919
- if (typeof toggle !== 'boolean') {
1920
- logger.debug('Invalid toggle value for tower fan switch');
1921
- return false;
1922
- }
1923
-
1924
- const [head, body] = this.buildApiDict('setSwitch');
1925
- body.payload.data = {
1926
- powerSwitch: toggle ? 1 : 0,
1927
- switchIdx: 0
1928
- };
1929
-
1930
- const [response, status] = await this.callApi(
1931
- '/cloud/v2/deviceManaged/bypassV2',
1932
- 'post',
1933
- body,
1934
- head
1935
- );
1936
-
1937
- const success = this.checkResponse([response, status], 'toggleSwitch');
1938
- if (success) {
1939
- this.deviceStatus = toggle ? 'on' : 'off';
1940
- } else {
1941
- logger.debug('Error toggling tower fan power');
1942
- }
1943
- return success;
1944
- }
1945
-
1946
- /**
1947
- * Turn device on
1948
- */
1949
- async turnOn(): Promise<boolean> {
1950
- logger.debug(`Turning on device: ${this.deviceName}`);
1951
- return this.toggleSwitch(true);
1952
- }
1953
-
1954
- /**
1955
- * Turn device off
1956
- */
1957
- async turnOff(): Promise<boolean> {
1958
- logger.debug(`Turning off device: ${this.deviceName}`);
1959
- return this.toggleSwitch(false);
1960
- }
1961
-
1962
- /**
1963
- * Set tower fan mode
1964
- */
1965
- async mode_toggle(mode: string): Promise<boolean> {
1966
- if (!this.towerModes.includes(mode as any)) {
1967
- logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.towerModes.join(', ')}`);
1968
- return false;
1969
- }
1970
-
1971
- if (mode === 'off') {
1972
- return this.turnOff();
1973
- }
1974
-
1975
- const [head, body] = this.buildApiDict('setTowerFanMode');
1976
- body.deviceId = this.cid;
1977
- body.payload.data = {
1978
- workMode: mode
1979
- };
1980
-
1981
- const [response, status] = await this.callApi(
1982
- '/cloud/v2/deviceManaged/bypassV2',
1983
- 'post',
1984
- body,
1985
- head
1986
- );
1987
-
1988
- const success = this.checkResponse([response, status], 'mode_toggle');
1989
- if (success) {
1990
- this.details.workMode = mode;
1991
- } else {
1992
- logger.error(`Failed to set ${mode} mode for device: ${this.deviceName}`);
1993
- }
1994
- return success;
1995
- }
1996
-
1997
- /**
1998
- * Change fan speed
1999
- */
2000
- async changeFanSpeed(speed: number): Promise<boolean> {
2001
- // const speeds = this.config.levels ?? [];
2002
- // if (!speeds.includes(speed)) {
2003
- // logger.debug(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')}`);
2004
- // return false;
2005
- // }
2006
-
2007
- const [head, body] = this.buildApiDict('setLevel');
2008
- body.payload.data = {
2009
- levelIdx: 0,
2010
- levelType: 'wind',
2011
- manualSpeedLevel: speed
2012
- };
2013
-
2014
- const [response, status] = await this.callApi(
2015
- '/cloud/v2/deviceManaged/bypassV2',
2016
- 'post',
2017
- body,
2018
- head
2019
- );
2020
-
2021
- const success = this.checkResponse([response, status], 'changeFanSpeed');
2022
- if (success) {
2023
- this.details.manualSpeedLevel = speed;
2024
- } else {
2025
- logger.debug('Error setting fan speed');
2026
- }
2027
- return success;
2028
- }
2029
-
2030
- /**
2031
- * Set normal mode
2032
- */
2033
- async normal_mode(): Promise<boolean> {
2034
- logger.debug(`Setting normal mode for device: ${this.deviceName}`);
2035
- return this.mode_toggle('normal');
2036
- }
2037
-
2038
- /**
2039
- * Set manual mode - adapter to set mode to normal
2040
- */
2041
- async manual_mode(): Promise<boolean> {
2042
- logger.debug(`Setting manual mode for device: ${this.deviceName}`);
2043
- return this.normal_mode();
2044
- }
2045
-
2046
- /**
2047
- * Set advanced sleep mode
2048
- */
2049
- async advanced_sleep_mode(): Promise<boolean> {
2050
- logger.debug(`Setting advanced sleep mode for device: ${this.deviceName}`);
2051
- return this.mode_toggle('advancedSleep');
2052
- }
2053
-
2054
- /**
2055
- * Set sleep mode - adapter for advanced sleep mode
2056
- */
2057
- async sleep_mode(): Promise<boolean> {
2058
- logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
2059
- return this.advanced_sleep_mode();
2060
- }
2061
-
2062
- /**
2063
- * Override setMode to handle tower fan specific modes
2064
- */
2065
- async setMode(mode: string): Promise<boolean> {
2066
- return this.mode_toggle(mode);
2067
- }
2068
-
2069
- /**
2070
- * Get current speed
2071
- */
2072
- get speed(): number {
2073
- return this.details.manualSpeedLevel || 0;
2074
- }
2075
-
2076
- /**
2077
- * Get display status
2078
- */
2079
- get displayState(): boolean {
2080
- return Boolean(this.details.screenState);
2081
- }
2082
-
2083
- /**
2084
- * Get oscillation status
2085
- */
2086
- get oscillationState(): boolean {
2087
- return Boolean(this.details.oscillationState);
2088
- }
2089
-
2090
- /**
2091
- * Get mute status
2092
- */
2093
- get muteState(): boolean {
2094
- return Boolean(this.details.muteState);
2095
- }
2096
-
2097
- /**
2098
- * Get temperature (in tenths of a degree)
2099
- */
2100
- get temperature(): number {
2101
- return this.details.temperature || 0;
2102
- }
2103
-
2104
- /**
2105
- * Get humidity percentage
2106
- */
2107
- get humidity(): number {
2108
- return this.details.humidity || 0;
2109
- }
2110
-
2111
- /**
2112
- * Get thermal comfort level
2113
- */
2114
- get thermalComfort(): number {
2115
- return this.details.thermalComfort || 0;
2116
- }
2117
-
2118
- /**
2119
- * Get sleep preferences
2120
- */
2121
- get sleepPreference(): Record<string, any> {
2122
- return this.details.sleepPreference || {};
2123
- }
2124
-
2125
- /**
2126
- * Get schedule count
2127
- */
2128
- get scheduleCount(): number {
2129
- return this.details.scheduleCount || 0;
2130
- }
2131
-
2132
- /**
2133
- * Get error code
2134
- */
2135
- get errorCode(): number {
2136
- return this.details.errorCode || 0;
2137
- }
2138
-
2139
- /**
2140
- * Turn oscillation on
2141
- */
2142
- async setOscillationOn(): Promise<boolean> {
2143
- return this.setOscillation(true);
2144
- }
2145
-
2146
- /**
2147
- * Turn oscillation off
2148
- */
2149
- async setOscillationOff(): Promise<boolean> {
2150
- return this.setOscillation(false);
2151
- }
2152
-
2153
- /**
2154
- * Display device info
2155
- */
2156
- override display(): void {
2157
- super.display();
2158
- const info = [
2159
- ['Mode: ', this.details.workMode || ''],
2160
- ['Speed: ', this.speed],
2161
- ['Fan Speed Level: ', this.details.fanSpeedLevel || 0],
2162
- ['Display: ', this.displayState],
2163
- ['Display Switch: ', this.details.screenSwitch],
2164
- ['Oscillation: ', this.oscillationState],
2165
- ['Oscillation Switch: ', this.details.oscillationSwitch],
2166
- ['Mute: ', this.muteState],
2167
- ['Mute Switch: ', this.details.muteSwitch],
2168
- ['Timer: ', this.timer],
2169
- ['Temperature: ', this.temperature / 10, '°C'],
2170
- ['Humidity: ', this.humidity, '%'],
2171
- ['Thermal Comfort: ', this.thermalComfort],
2172
- ['Sleep Preference: ', JSON.stringify(this.sleepPreference)],
2173
- ['Schedule Count: ', this.scheduleCount],
2174
- ['Displaying Type: ', this.details.displayingType],
2175
- ['Error Code: ', this.errorCode]
2176
- ];
2177
-
2178
- for (const [key, value, unit = ''] of info) {
2179
- logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
2180
- }
2181
- }
2182
- }
2183
-
2184
- // Export fan modules dictionary
2185
- export const fanModules: Record<string, new (details: Record<string, any>, manager: VeSync) => VeSyncFan> = {
2186
- // Core Series
2187
- 'Core200S': VeSyncAirBypass,
2188
- 'Core300S': VeSyncAirBypass,
2189
- 'Core400S': VeSyncAirBypass,
2190
- 'Core600S': VeSyncAirBypass,
2191
-
2192
- // LAP Series
2193
- 'LAP-C201S-AUSR': VeSyncAirBypass,
2194
- 'LAP-C202S-WUSR': VeSyncAirBypass,
2195
- 'LAP-C301S-WJP': VeSyncAirBypass,
2196
- 'LAP-C302S-WUSB': VeSyncAirBypass,
2197
- 'LAP-C301S-WAAA': VeSyncAirBypass,
2198
- 'LAP-C401S-WJP': VeSyncAirBypass,
2199
- 'LAP-C401S-WUSR': VeSyncAirBypass,
2200
- 'LAP-C401S-WAAA': VeSyncAirBypass,
2201
- 'LAP-C601S-WUS': VeSyncAirBypass,
2202
- 'LAP-C601S-WUSR': VeSyncAirBypass,
2203
- 'LAP-C601S-WEU': VeSyncAirBypass,
2204
- // Vital100S Series - Using VeSyncAirBaseV2
2205
- 'LAP-V102S-AASR': VeSyncAirBaseV2,
2206
- 'LAP-V102S-WUS': VeSyncAirBaseV2,
2207
- 'LAP-V102S-WEU': VeSyncAirBaseV2,
2208
- 'LAP-V102S-AUSR': VeSyncAirBaseV2,
2209
- 'LAP-V102S-WJP': VeSyncAirBaseV2,
2210
- // Vital200S Series - Using VeSyncAirBaseV2
2211
- 'LAP-V201S-AASR': VeSyncAirBaseV2,
2212
- 'LAP-V201S-WJP': VeSyncAirBaseV2,
2213
- 'LAP-V201S-WEU': VeSyncAirBaseV2,
2214
- 'LAP-V201S-WUS': VeSyncAirBaseV2,
2215
- 'LAP-V201-AUSR': VeSyncAirBaseV2,
2216
- 'LAP-V201S-AUSR': VeSyncAirBaseV2,
2217
- 'LAP-V201S-AEUR': VeSyncAirBaseV2,
2218
- // EverestAir Series - Using VeSyncAirBaseV2
2219
- 'LAP-EL551S-AUS': VeSyncAirBaseV2,
2220
- 'LAP-EL551S-AEUR': VeSyncAirBaseV2,
2221
- 'LAP-EL551S-WEU': VeSyncAirBaseV2,
2222
- 'LAP-EL551S-WUS': VeSyncAirBaseV2,
2223
-
2224
- // LTF Series
2225
- 'LTF-F422S-KEU': VeSyncTowerFan,
2226
- 'LTF-F422S-WUSR': VeSyncTowerFan,
2227
- 'LTF-F422_WJP': VeSyncTowerFan,
2228
- 'LTF-F422S-WUS': VeSyncTowerFan,
2229
-
2230
- // Classic Series
2231
- 'Classic300S': VeSyncHumidifier,
2232
- 'Classic200S': VeSyncHumidifier,
2233
-
2234
- // Dual Series
2235
- 'Dual200S': VeSyncHumidifier,
2236
-
2237
- // LUH Series
2238
- 'LUH-A601S-WUSB': VeSyncWarmHumidifier,
2239
- 'LUH-A601S-AUSW': VeSyncWarmHumidifier,
2240
- 'LUH-D301S-WUSR': VeSyncWarmHumidifier,
2241
- 'LUH-D301S-WJP': VeSyncWarmHumidifier,
2242
- 'LUH-D301S-WEU': VeSyncWarmHumidifier,
2243
- 'LUH-A602S-WUSR': VeSyncWarmHumidifier,
2244
- 'LUH-A602S-WUS': VeSyncWarmHumidifier,
2245
- 'LUH-A602S-WEUR': VeSyncWarmHumidifier,
2246
- 'LUH-A602S-WEU': VeSyncWarmHumidifier,
2247
- 'LUH-A602S-WJP': VeSyncWarmHumidifier,
2248
- 'LUH-A602S-WUSC': VeSyncWarmHumidifier,
2249
- 'LUH-O451S-WEU': VeSyncWarmHumidifier,
2250
- 'LUH-O451S-WUS': VeSyncWarmHumidifier,
2251
- 'LUH-O451S-WUSR': VeSyncWarmHumidifier,
2252
- 'LUH-O601S-WUS': VeSyncWarmHumidifier,
2253
- 'LUH-O601S-KUS': VeSyncWarmHumidifier,
2254
- 'LUH-M101S-WUS': VeSyncWarmHumidifier,
2255
- 'LUH-M101S-WEUR': VeSyncWarmHumidifier,
2256
-
2257
- // LEH Series
2258
- 'LEH-S601S-WUS': VeSyncWarmHumidifier,
2259
- 'LEH-S601S-WUSR': VeSyncWarmHumidifier,
2260
-
2261
- // LV Series
2262
- 'LV-PUR131S': VeSyncAirBypass,
2263
- 'LV-RH131S': VeSyncAirBypass
2264
- };
5
+ // Re-export all fan implementations from the fans directory
6
+ export * from './fans';