tsvesync 1.0.64 → 1.0.66

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,2265 +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: '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';