tsvesync 1.0.42 → 1.0.44

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.
@@ -28,7 +28,7 @@ export class VeSyncAirBypass extends VeSyncFan {
28
28
  const head = Helpers.reqHeaderBypass();
29
29
  const body = {
30
30
  ...Helpers.reqBody(this.manager, 'bypassV2'),
31
- deviceId: this.cid,
31
+ cid: this.cid,
32
32
  configModule: this.configModule,
33
33
  payload: {
34
34
  method,
@@ -533,53 +533,13 @@ export class VeSyncAirBypass extends VeSyncFan {
533
533
  }
534
534
 
535
535
  /**
536
- * Set mode
536
+ * Set auto mode
537
537
  */
538
- async mode_toggle(mode: string): Promise<boolean> {
539
- if (!this.modes.includes(mode.toLowerCase() as any)) {
540
- logger.debug(`Invalid purifier mode used - ${mode}`);
541
- return false;
542
- }
543
-
544
- const [head, body] = this.buildApiDict('setPurifierMode');
545
- if (!head || !body) {
546
- return false;
547
- }
548
-
549
- if (mode === 'manual') {
550
- body.payload = {
551
- data: {
552
- id: 0,
553
- level: 1,
554
- type: 'wind'
555
- },
556
- method: 'setLevel',
557
- type: 'APP'
558
- };
559
- } else {
560
- body.payload.data = {
561
- mode: mode.toLowerCase()
562
- };
563
- }
564
-
565
- const [response, status] = await this.callApi(
566
- '/cloud/v2/deviceManaged/bypassV2',
567
- 'post',
568
- body,
569
- head
570
- );
571
-
572
- const success = this.checkResponse([response, status], 'mode_toggle');
573
- if (success) {
574
- if (mode === 'manual') {
575
- this.details.speed = 1;
576
- this.details.mode = 'manual';
577
- } else {
578
- this.details.mode = mode;
579
- this.details.speed = 0;
580
- }
581
- } else {
582
- logger.debug('Error setting purifier mode');
538
+ async autoMode(): Promise<boolean> {
539
+ logger.debug(`Setting auto mode for device: ${this.deviceName}`);
540
+ const success = await this.setMode('auto');
541
+ if (!success) {
542
+ logger.error(`Failed to set auto mode for device: ${this.deviceName}`);
583
543
  }
584
544
  return success;
585
545
  }
@@ -587,34 +547,25 @@ export class VeSyncAirBypass extends VeSyncFan {
587
547
  /**
588
548
  * Set manual mode
589
549
  */
590
- async manual_mode(): Promise<boolean> {
591
- if (!this.modes.includes('manual')) {
592
- logger.debug(`${this.deviceName} does not have manual mode`);
593
- return false;
550
+ async manualMode(): Promise<boolean> {
551
+ logger.debug(`Setting manual mode for device: ${this.deviceName}`);
552
+ const success = await this.setMode('manual');
553
+ if (!success) {
554
+ logger.error(`Failed to set manual mode for device: ${this.deviceName}`);
594
555
  }
595
- return this.mode_toggle('manual');
556
+ return success;
596
557
  }
597
558
 
598
559
  /**
599
560
  * Set sleep mode
600
561
  */
601
- async sleep_mode(): Promise<boolean> {
602
- if (!this.modes.includes('sleep')) {
603
- logger.debug(`${this.deviceName} does not have sleep mode`);
604
- return false;
605
- }
606
- return this.mode_toggle('sleep');
607
- }
608
-
609
- /**
610
- * Set auto mode
611
- */
612
- async auto_mode(): Promise<boolean> {
613
- if (!this.modes.includes('auto')) {
614
- logger.debug(`${this.deviceName} does not have auto mode`);
615
- return false;
562
+ async sleepMode(): Promise<boolean> {
563
+ logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
564
+ const success = await this.setMode('sleep');
565
+ if (!success) {
566
+ logger.error(`Failed to set sleep mode for device: ${this.deviceName}`);
616
567
  }
617
- return this.mode_toggle('auto');
568
+ return success;
618
569
  }
619
570
 
620
571
  /**
@@ -640,26 +591,6 @@ export class VeSyncAirBypass extends VeSyncFan {
640
591
  }
641
592
  return success;
642
593
  }
643
-
644
- /**
645
- * Display device info
646
- */
647
- override display(): void {
648
- super.display();
649
- const info = [
650
- ['Mode: ', this.details.mode || ''],
651
- ['Speed: ', this.speed],
652
- ['Filter Life: ', this.filterLife || 0, '%'],
653
- ['Air Quality: ', this.details.air_quality || ''],
654
- ['Display: ', this.details.display || false],
655
- ['Child Lock: ', this.details.child_lock || false],
656
- ['Night Light: ', this.details.night_light || 'off']
657
- ];
658
-
659
- for (const [key, value, unit = ''] of info) {
660
- logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
661
- }
662
- }
663
594
  }
664
595
 
665
596
  /**
@@ -1120,18 +1051,6 @@ export class VeSyncHumidifier extends VeSyncFan {
1120
1051
  return success;
1121
1052
  }
1122
1053
 
1123
- /**
1124
- * Set sleep mode
1125
- */
1126
- async sleepMode(): Promise<boolean> {
1127
- logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
1128
- const success = await this.setMode('sleep');
1129
- if (!success) {
1130
- logger.error(`Failed to set sleep mode for device: ${this.deviceName}`);
1131
- }
1132
- return success;
1133
- }
1134
-
1135
1054
  /**
1136
1055
  * Turn off display
1137
1056
  */
@@ -1359,12 +1278,48 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1359
1278
  export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1360
1279
  protected _lightDetection: boolean = false;
1361
1280
  protected _lightDetectionState: boolean = false;
1281
+ protected setSpeedLevel: number | null = null;
1282
+ protected autoPreferences: string[] = ['default', 'efficient', 'quiet'];
1283
+ protected enabled: boolean = false;
1284
+ protected _mode: string = '';
1285
+ protected _speed: number = 0;
1286
+ protected _timer: { duration: number; action: string } | null = null;
1362
1287
 
1363
1288
  constructor(details: Record<string, any>, manager: VeSync) {
1364
1289
  super(details, manager);
1365
1290
  logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
1366
1291
  }
1367
1292
 
1293
+ protected buildConfigDict(configDict: Record<string, any>): void {
1294
+ if (configDict) {
1295
+ this.config = configDict;
1296
+ }
1297
+ }
1298
+
1299
+ get mode(): string {
1300
+ return this._mode;
1301
+ }
1302
+
1303
+ set mode(value: string) {
1304
+ this._mode = value;
1305
+ }
1306
+
1307
+ get speed(): number {
1308
+ return this._speed;
1309
+ }
1310
+
1311
+ set speed(value: number) {
1312
+ this._speed = value;
1313
+ }
1314
+
1315
+ get timer(): { duration: number; action: string } | null {
1316
+ return this._timer;
1317
+ }
1318
+
1319
+ set timer(value: { duration: number; action: string } | null) {
1320
+ this._timer = value;
1321
+ }
1322
+
1368
1323
  /**
1369
1324
  * Build API dictionary
1370
1325
  */
@@ -1372,7 +1327,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1372
1327
  const head = Helpers.reqHeaderBypass();
1373
1328
  const body = {
1374
1329
  ...Helpers.reqBody(this.manager, 'bypassV2'),
1375
- deviceId: this.cid,
1330
+ cid: this.cid,
1376
1331
  configModule: this.configModule,
1377
1332
  payload: {
1378
1333
  method,
@@ -1383,25 +1338,100 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1383
1338
  return [head, body];
1384
1339
  }
1385
1340
 
1386
- get lightDetection(): boolean {
1387
- return this._lightDetection;
1388
- }
1341
+ /**
1342
+ * Get device details
1343
+ */
1344
+ async getDetails(): Promise<Boolean> {
1345
+ logger.debug(`Getting details for device: ${this.deviceName}`);
1346
+ const [head, body] = this.buildApiDict('getPurifierStatus');
1389
1347
 
1390
- set lightDetection(toggle: boolean) {
1391
- this._lightDetection = toggle;
1348
+ const [response, status] = await this.callApi(
1349
+ '/cloud/v2/deviceManaged/bypassV2',
1350
+ 'post',
1351
+ body,
1352
+ head
1353
+ );
1354
+
1355
+ if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
1356
+ logger.debug('Error getting purifier details');
1357
+ this.connectionStatus = 'offline';
1358
+ return false;
1359
+ }
1360
+
1361
+ const innerResponse = response.result;
1362
+ if (innerResponse.code !== 0 || !innerResponse.result) {
1363
+ logger.debug('Error in inner response from purifier');
1364
+ this.connectionStatus = 'offline';
1365
+ return false;
1366
+ }
1367
+
1368
+ const deviceData = innerResponse.result;
1369
+ this.buildPurifierDict(deviceData);
1370
+ if (deviceData.configuration) {
1371
+ this.buildConfigDict(deviceData.configuration);
1372
+ }
1373
+ return true;
1392
1374
  }
1393
1375
 
1394
- get lightDetectionState(): boolean {
1395
- return this._lightDetectionState;
1376
+ /**
1377
+ * Build purifier dictionary
1378
+ */
1379
+ protected buildPurifierDict(devDict: Record<string, any>): void {
1380
+ this.connectionStatus = 'online';
1381
+ const powerSwitch = Boolean(devDict.powerSwitch ?? 0);
1382
+ this.enabled = powerSwitch;
1383
+ this.deviceStatus = powerSwitch ? 'on' : 'off';
1384
+ this.mode = devDict.workMode ?? 'manual';
1385
+
1386
+ this.speed = devDict.fanSpeedLevel ?? 0;
1387
+ this.setSpeedLevel = devDict.manualSpeedLevel ?? 1;
1388
+
1389
+ this.details = {
1390
+ ...this.details,
1391
+ filter_life: devDict.filterLifePercent ?? 0,
1392
+ child_lock: Boolean(devDict.childLockSwitch ?? 0),
1393
+ display: Boolean(devDict.screenState ?? 0),
1394
+ light_detection_switch: Boolean(devDict.lightDetectionSwitch ?? 0),
1395
+ environment_light_state: Boolean(devDict.environmentLightState ?? 0),
1396
+ screen_switch: Boolean(devDict.screenSwitch ?? 0)
1397
+ };
1398
+
1399
+ if (this.hasFeature('air_quality')) {
1400
+ this.details.air_quality_value = devDict.PM25 ?? 0;
1401
+ this.details.air_quality = devDict.AQLevel ?? 0;
1402
+ }
1403
+
1404
+ if ('PM1' in devDict) this.details.pm1 = devDict.PM1;
1405
+ if ('PM10' in devDict) this.details.pm10 = devDict.PM10;
1406
+ if ('AQPercent' in devDict) this.details.aq_percent = devDict.AQPercent;
1407
+ if ('fanRotateAngle' in devDict) this.details.fan_rotate_angle = devDict.fanRotateAngle;
1408
+ if ('filterOpenState' in devDict) this.details.filter_open_state = Boolean(devDict.filterOpenState);
1409
+
1410
+ if ((devDict.timerRemain ?? 0) > 0) {
1411
+ this.timer = { duration: devDict.timerRemain, action: 'off' };
1412
+ }
1413
+
1414
+ if (typeof devDict.autoPreference === 'object' && devDict.autoPreference) {
1415
+ this.details.auto_preference_type = devDict.autoPreference?.autoPreferenceType ?? 'default';
1416
+ } else {
1417
+ this.details.auto_preference_type = null;
1418
+ }
1396
1419
  }
1397
1420
 
1398
- async setLightDetection(toggle: boolean): Promise<boolean> {
1399
- const [head, body] = this.buildApiDict('setLightDetection');
1400
- if (!head || !body) {
1421
+ /**
1422
+ * Toggle device power
1423
+ */
1424
+ async toggleSwitch(toggle: boolean): Promise<boolean> {
1425
+ if (typeof toggle !== 'boolean') {
1426
+ logger.debug('Invalid toggle value for purifier switch');
1401
1427
  return false;
1402
1428
  }
1403
1429
 
1404
- body.payload.data = { enabled: toggle };
1430
+ const [head, body] = this.buildApiDict('setSwitch');
1431
+ body.payload.data = {
1432
+ powerSwitch: toggle ? 1 : 0,
1433
+ switchIdx: 0
1434
+ };
1405
1435
 
1406
1436
  const [response, status] = await this.callApi(
1407
1437
  '/cloud/v2/deviceManaged/bypassV2',
@@ -1410,55 +1440,198 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1410
1440
  head
1411
1441
  );
1412
1442
 
1413
- const success = this.checkResponse([response, status], 'setLightDetection');
1443
+ const success = this.checkResponse([response, status], 'toggleSwitch');
1414
1444
  if (success) {
1415
- this._lightDetection = toggle;
1445
+ this.deviceStatus = toggle ? 'on' : 'off';
1416
1446
  }
1417
1447
  return success;
1418
1448
  }
1419
1449
 
1420
- async setLightDetectionOn(): Promise<boolean> {
1421
- return this.setLightDetection(true);
1450
+ /**
1451
+ * Turn device on - compatibility method that proxies to toggleSwitch
1452
+ */
1453
+ async turnOn(): Promise<boolean> {
1454
+ logger.info(`Turning on device: ${this.deviceName}`);
1455
+ return this.toggleSwitch(true);
1456
+ }
1457
+
1458
+ /**
1459
+ * Turn device off - compatibility method that proxies to toggleSwitch
1460
+ */
1461
+ async turnOff(): Promise<boolean> {
1462
+ logger.info(`Turning off device: ${this.deviceName}`);
1463
+ return this.toggleSwitch(false);
1422
1464
  }
1423
1465
 
1424
- async setLightDetectionOff(): Promise<boolean> {
1425
- return this.setLightDetection(false);
1466
+ /**
1467
+ * Change fan speed
1468
+ */
1469
+ async changeFanSpeed(speed: number): Promise<boolean> {
1470
+ const speeds: number[] = this.config?.levels ?? [];
1471
+ const currentSpeed = this.setSpeedLevel ?? 0;
1472
+
1473
+ if (speed !== undefined) {
1474
+ if (!speeds.includes(speed)) {
1475
+ logger.debug(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')}`);
1476
+ return false;
1477
+ }
1478
+ } else {
1479
+ if (currentSpeed === speeds[speeds.length - 1] || currentSpeed === 0) {
1480
+ speed = speeds[0];
1481
+ } else {
1482
+ const currentIndex = speeds.indexOf(currentSpeed);
1483
+ speed = speeds[currentIndex + 1];
1484
+ }
1485
+ }
1486
+
1487
+ const [head, body] = this.buildApiDict('setLevel');
1488
+ body.payload.data = {
1489
+ levelIdx: 0,
1490
+ manualSpeedLevel: speed,
1491
+ levelType: 'wind'
1492
+ };
1493
+
1494
+ const [response, status] = await this.callApi(
1495
+ '/cloud/v2/deviceManaged/bypassV2',
1496
+ 'post',
1497
+ body,
1498
+ head
1499
+ );
1500
+
1501
+ const success = this.checkResponse([response, status], 'changeFanSpeed');
1502
+ if (success) {
1503
+ this.setSpeedLevel = speed;
1504
+ this.mode = 'manual';
1505
+ }
1506
+ return success;
1426
1507
  }
1427
1508
 
1428
- async turboMode(): Promise<boolean> {
1429
- return this.mode_toggle('turbo');
1509
+ /**
1510
+ * Set mode
1511
+ */
1512
+ async setMode(mode: string): Promise<boolean> {
1513
+ if (!this.modes.includes(mode as any)) {
1514
+ logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`);
1515
+ return false;
1516
+ }
1517
+
1518
+ if (mode === 'manual') {
1519
+ if (!this.speed || this.speed === 0) {
1520
+ return this.changeFanSpeed(1);
1521
+ }
1522
+ return this.changeFanSpeed(this.speed);
1523
+ }
1524
+
1525
+ if (mode === 'off') {
1526
+ return this.turnOff();
1527
+ }
1528
+
1529
+ const [head, body] = this.buildApiDict('setPurifierMode');
1530
+ body.payload.data = {
1531
+ workMode: mode
1532
+ };
1533
+
1534
+ const [response, status] = await this.callApi(
1535
+ '/cloud/v2/deviceManaged/bypassV2',
1536
+ 'post',
1537
+ body,
1538
+ head
1539
+ );
1540
+
1541
+ const success = this.checkResponse([response, status], 'setMode');
1542
+ if (success) {
1543
+ this.mode = mode;
1544
+ }
1545
+ return success;
1430
1546
  }
1431
1547
 
1432
- async petMode(): Promise<boolean> {
1433
- return this.mode_toggle('pet');
1548
+ /**
1549
+ * Set display status
1550
+ */
1551
+ async setDisplay(enabled: boolean): Promise<boolean> {
1552
+ if (!this.hasFeature('display')) {
1553
+ const error = 'Display control not supported';
1554
+ logger.error(`${error} for device: ${this.deviceName}`);
1555
+ throw new Error(error);
1556
+ }
1557
+
1558
+ const [head, body] = this.buildApiDict('setDisplay');
1559
+ body.payload.data = {
1560
+ screenSwitch: enabled ? 1 : 0
1561
+ };
1562
+
1563
+ const [response, status] = await this.callApi(
1564
+ '/cloud/v2/deviceManaged/bypassV2',
1565
+ 'post',
1566
+ body,
1567
+ head
1568
+ );
1569
+
1570
+ const success = this.checkResponse([response, status], 'setDisplay');
1571
+ if (success) {
1572
+ this.details.screen_switch = enabled;
1573
+ }
1574
+ return success;
1434
1575
  }
1435
1576
 
1436
1577
  /**
1437
- * Build configuration dictionary
1578
+ * Set child lock
1438
1579
  */
1439
- protected buildConfigDict(configDict: Record<string, any>): void {
1440
- if (configDict) {
1441
- this.config = configDict;
1580
+ async setChildLock(enabled: boolean): Promise<boolean> {
1581
+ if (!this.hasFeature('child_lock')) {
1582
+ const error = 'Child lock not supported';
1583
+ logger.error(`${error} for device: ${this.deviceName}`);
1584
+ throw new Error(error);
1442
1585
  }
1586
+
1587
+ const [head, body] = this.buildApiDict('setChildLock');
1588
+ body.payload.data = {
1589
+ childLockSwitch: enabled ? 1 : 0
1590
+ };
1591
+
1592
+ const [response, status] = await this.callApi(
1593
+ '/cloud/v2/deviceManaged/bypassV2',
1594
+ 'post',
1595
+ body,
1596
+ head
1597
+ );
1598
+
1599
+ const success = this.checkResponse([response, status], 'setChildLock');
1600
+ if (success) {
1601
+ this.details.child_lock = enabled;
1602
+ }
1603
+ return success;
1443
1604
  }
1444
1605
 
1445
1606
  /**
1446
- * Set mode
1607
+ * Set timer
1447
1608
  */
1448
- public async mode_toggle(mode: string): Promise<boolean> {
1449
- const validModes = this.modes.map(m => m.toLowerCase());
1450
- if (!validModes.includes(mode.toLowerCase())) {
1451
- logger.debug(`Invalid mode used - ${mode}`);
1452
- return false;
1609
+ async setTimer(timerDuration: number, action: 'on' | 'off' = 'off', method: string = 'powerSwitch'): Promise<boolean> {
1610
+ if (!this.hasFeature('timer')) {
1611
+ const error = 'Timer not supported';
1612
+ logger.error(`${error} for device: ${this.deviceName}`);
1613
+ throw new Error(error);
1453
1614
  }
1454
1615
 
1455
- const [head, body] = this.buildApiDict('setPurifierMode');
1456
- if (!head || !body) {
1616
+ if (!['on', 'off'].includes(action)) {
1617
+ logger.debug('Invalid action for timer');
1618
+ return false;
1619
+ }
1620
+ if (method !== 'powerSwitch') {
1621
+ logger.debug('Invalid method for timer');
1457
1622
  return false;
1458
1623
  }
1459
1624
 
1625
+ const [head, body] = this.buildApiDict('addTimerV2');
1626
+ body.payload.subDeviceNo = 0;
1460
1627
  body.payload.data = {
1461
- mode: mode
1628
+ startAct: [{
1629
+ type: method,
1630
+ num: 0,
1631
+ act: action === 'on' ? 1 : 0
1632
+ }],
1633
+ total: timerDuration,
1634
+ subDeviceNo: 0
1462
1635
  };
1463
1636
 
1464
1637
  const [response, status] = await this.callApi(
@@ -1468,45 +1641,150 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1468
1641
  head
1469
1642
  );
1470
1643
 
1471
- const success = this.checkResponse([response, status], 'mode_toggle');
1644
+ const success = this.checkResponse([response, status], 'setTimer');
1472
1645
  if (success) {
1473
- this.details.mode = mode;
1474
- } else {
1475
- logger.debug('Error setting mode');
1646
+ this.timer = { duration: timerDuration, action };
1476
1647
  }
1477
1648
  return success;
1478
1649
  }
1479
1650
 
1480
1651
  /**
1481
- * Set manual mode
1652
+ * Clear timer
1482
1653
  */
1483
- async manual_mode(): Promise<boolean> {
1484
- return this.mode_toggle('manual');
1654
+ async clearTimer(): Promise<boolean> {
1655
+ if (!this.hasFeature('timer')) {
1656
+ const error = 'Timer not supported';
1657
+ logger.error(`${error} for device: ${this.deviceName}`);
1658
+ throw new Error(error);
1659
+ }
1660
+
1661
+ const [head, body] = this.buildApiDict('delTimerV2');
1662
+ body.payload.subDeviceNo = 0;
1663
+ body.payload.data = { id: 1, subDeviceNo: 0 };
1664
+
1665
+ const [response, status] = await this.callApi(
1666
+ '/cloud/v2/deviceManaged/bypassV2',
1667
+ 'post',
1668
+ body,
1669
+ head
1670
+ );
1671
+
1672
+ const success = this.checkResponse([response, status], 'clearTimer');
1673
+ if (success) {
1674
+ this.timer = null;
1675
+ }
1676
+ return success;
1485
1677
  }
1486
1678
 
1487
1679
  /**
1488
- * Set sleep mode
1680
+ * Set auto preference
1489
1681
  */
1490
- async sleep_mode(): Promise<boolean> {
1491
- return this.mode_toggle('sleep');
1682
+ async setAutoPreference(preference: string = 'default', roomSize: number = 600): Promise<boolean> {
1683
+ if (!this.autoPreferences.includes(preference)) {
1684
+ logger.debug(`Invalid preference: ${preference} - valid preferences are default, efficient, quiet`);
1685
+ return false;
1686
+ }
1687
+
1688
+ const [head, body] = this.buildApiDict('setAutoPreference');
1689
+ body.payload.data = {
1690
+ autoPreference: preference,
1691
+ roomSize: roomSize
1692
+ };
1693
+
1694
+ const [response, status] = await this.callApi(
1695
+ '/cloud/v2/deviceManaged/bypassV2',
1696
+ 'post',
1697
+ body,
1698
+ head
1699
+ );
1700
+
1701
+ const success = this.checkResponse([response, status], 'setAutoPreference');
1702
+ if (success) {
1703
+ this.details.auto_preference = preference;
1704
+ }
1705
+ return success;
1492
1706
  }
1493
1707
 
1494
1708
  /**
1495
- * Set auto mode
1709
+ * Set light detection
1496
1710
  */
1497
- async auto_mode(): Promise<boolean> {
1498
- if (!this.modes.includes('auto')) {
1499
- logger.debug(`${this.deviceName} does not have auto mode`);
1500
- return false;
1711
+ async setLightDetection(enabled: boolean): Promise<boolean> {
1712
+ const [head, body] = this.buildApiDict('setLightDetection');
1713
+ body.payload.data = {
1714
+ lightDetectionSwitch: enabled ? 1 : 0
1715
+ };
1716
+
1717
+ const [response, status] = await this.callApi(
1718
+ '/cloud/v2/deviceManaged/bypassV2',
1719
+ 'post',
1720
+ body,
1721
+ head
1722
+ );
1723
+
1724
+ const success = this.checkResponse([response, status], 'setLightDetection');
1725
+ if (success) {
1726
+ this.details.light_detection_switch = enabled;
1501
1727
  }
1502
- return this.mode_toggle('auto');
1728
+ return success;
1729
+ }
1730
+
1731
+ /**
1732
+ * Get light detection status
1733
+ */
1734
+ get lightDetection(): boolean {
1735
+ return Boolean(this.details.light_detection_switch);
1736
+ }
1737
+
1738
+ /**
1739
+ * Get light detection state
1740
+ */
1741
+ get lightDetectionState(): boolean {
1742
+ return Boolean(this.details.environment_light_state);
1503
1743
  }
1504
1744
 
1505
1745
  /**
1506
- * Set turbo mode
1746
+ * Get auto preference type
1507
1747
  */
1508
- async turbo_mode(): Promise<boolean> {
1509
- return this.mode_toggle('turbo');
1748
+ get autoPreferenceType(): string | null {
1749
+ return this.details.auto_preference_type;
1750
+ }
1751
+
1752
+ /**
1753
+ * Display JSON details
1754
+ */
1755
+ override displayJSON(): string {
1756
+ const baseInfo = JSON.parse(super.displayJSON());
1757
+ const details: Record<string, string> = {
1758
+ ...baseInfo,
1759
+ 'Mode': this.mode,
1760
+ 'Filter Life': this.details.filter_life.toString(),
1761
+ 'Fan Level': this.speed.toString(),
1762
+ 'Display On': this.details.display.toString(),
1763
+ 'Child Lock': this.details.child_lock.toString(),
1764
+ 'Display Set On': this.details.screen_switch.toString(),
1765
+ 'Light Detection Enabled': this.details.light_detection_switch.toString(),
1766
+ 'Environment Light State': this.details.environment_light_state.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);
1510
1788
  }
1511
1789
  }
1512
1790
 
@@ -1522,20 +1800,36 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1522
1800
  logger.debug(`Initialized VeSyncTowerFan device: ${this.deviceName}`);
1523
1801
  }
1524
1802
 
1803
+ /**
1804
+ * Get timer status (in seconds)
1805
+ */
1806
+ override get timer(): { duration: number; action: string } | null {
1807
+ const timerRemain = this.details.timerRemain || 0;
1808
+ return timerRemain > 0 ? { duration: timerRemain, action: 'off' } : null;
1809
+ }
1810
+
1811
+ override set timer(value: { duration: number; action: string } | null) {
1812
+ if (value) {
1813
+ this.details.timerRemain = value.duration;
1814
+ } else {
1815
+ this.details.timerRemain = 0;
1816
+ }
1817
+ }
1818
+
1525
1819
  /**
1526
1820
  * Build API dictionary
1527
1821
  */
1528
1822
  protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
1529
1823
  const head = Helpers.reqHeaderBypass();
1530
- const body = Helpers.reqBody(this.manager, 'bypassV2');
1531
- body.cid = this.cid;
1532
- body.deviceId = this.cid;
1533
- body.configModule = this.configModule;
1534
- body.configModel = this.configModule;
1535
- body.payload = {
1536
- method: method,
1537
- source: 'APP',
1538
- data: {}
1824
+ const body = {
1825
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
1826
+ cid: this.cid,
1827
+ configModule: this.configModule,
1828
+ payload: {
1829
+ method,
1830
+ source: 'APP',
1831
+ data: {}
1832
+ }
1539
1833
  };
1540
1834
  return [head, body];
1541
1835
  }
@@ -1776,13 +2070,6 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1776
2070
  return Boolean(this.details.muteState);
1777
2071
  }
1778
2072
 
1779
- /**
1780
- * Get timer status (in seconds)
1781
- */
1782
- get timer(): number {
1783
- return this.details.timerRemain || 0;
1784
- }
1785
-
1786
2073
  /**
1787
2074
  * Get temperature (in tenths of a degree)
1788
2075
  */
@@ -1911,22 +2198,25 @@ export const fanModules: Record<string, new (details: Record<string, any>, manag
1911
2198
  'LAP-C601S-WUS': VeSyncAirBypass,
1912
2199
  'LAP-C601S-WUSR': VeSyncAirBypass,
1913
2200
  'LAP-C601S-WEU': VeSyncAirBypass,
1914
- 'LAP-V102S-AASR': VeSyncAirBypass,
1915
- 'LAP-V102S-WUS': VeSyncAirBypass,
1916
- 'LAP-V102S-WEU': VeSyncAirBypass,
1917
- 'LAP-V102S-AUSR': VeSyncAirBypass,
1918
- 'LAP-V102S-WJP': VeSyncAirBypass,
1919
- 'LAP-V201S-AASR': VeSyncAirBypass,
1920
- 'LAP-V201S-WJP': VeSyncAirBypass,
1921
- 'LAP-V201S-WEU': VeSyncAirBypass,
1922
- 'LAP-V201S-WUS': VeSyncAirBypass,
1923
- 'LAP-V201-AUSR': VeSyncAirBypass,
1924
- 'LAP-V201S-AUSR': VeSyncAirBypass,
1925
- 'LAP-V201S-AEUR': VeSyncAirBypass,
1926
- 'LAP-EL551S-AUS': VeSyncAirBypass,
1927
- 'LAP-EL551S-AEUR': VeSyncAirBypass,
1928
- 'LAP-EL551S-WEU': VeSyncAirBypass,
1929
- 'LAP-EL551S-WUS': VeSyncAirBypass,
2201
+ // Vital100S Series - Using VeSyncAirBaseV2
2202
+ 'LAP-V102S-AASR': VeSyncAirBaseV2,
2203
+ 'LAP-V102S-WUS': VeSyncAirBaseV2,
2204
+ 'LAP-V102S-WEU': VeSyncAirBaseV2,
2205
+ 'LAP-V102S-AUSR': VeSyncAirBaseV2,
2206
+ 'LAP-V102S-WJP': VeSyncAirBaseV2,
2207
+ // Vital200S Series - Using VeSyncAirBaseV2
2208
+ 'LAP-V201S-AASR': VeSyncAirBaseV2,
2209
+ 'LAP-V201S-WJP': VeSyncAirBaseV2,
2210
+ 'LAP-V201S-WEU': VeSyncAirBaseV2,
2211
+ 'LAP-V201S-WUS': VeSyncAirBaseV2,
2212
+ 'LAP-V201-AUSR': VeSyncAirBaseV2,
2213
+ 'LAP-V201S-AUSR': VeSyncAirBaseV2,
2214
+ 'LAP-V201S-AEUR': VeSyncAirBaseV2,
2215
+ // EverestAir Series - Using VeSyncAirBaseV2
2216
+ 'LAP-EL551S-AUS': VeSyncAirBaseV2,
2217
+ 'LAP-EL551S-AEUR': VeSyncAirBaseV2,
2218
+ 'LAP-EL551S-WEU': VeSyncAirBaseV2,
2219
+ 'LAP-EL551S-WUS': VeSyncAirBaseV2,
1930
2220
 
1931
2221
  // LTF Series
1932
2222
  'LTF-F422S-KEU': VeSyncTowerFan,