tsvesync 1.0.49 → 1.0.51

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.
@@ -12,7 +12,6 @@ import { logger } from './logger';
12
12
  */
13
13
  export class VeSyncAirBypass extends VeSyncFan {
14
14
  protected readonly modes = ['auto', 'manual', 'sleep'] as const;
15
- protected readonly speeds = [1, 2, 3, 4, 5];
16
15
  protected readonly displayModes = ['on', 'off'] as const;
17
16
  protected readonly childLockModes = ['on', 'off'] as const;
18
17
 
@@ -29,6 +28,7 @@ export class VeSyncAirBypass extends VeSyncFan {
29
28
  const body = {
30
29
  ...Helpers.reqBody(this.manager, 'bypassV2'),
31
30
  cid: this.cid,
31
+ deviceId: this.cid, // Add deviceId to match YAML spec
32
32
  configModule: this.configModule,
33
33
  payload: {
34
34
  method,
@@ -258,10 +258,10 @@ export class VeSyncAirBypass extends VeSyncFan {
258
258
  * Change fan speed
259
259
  */
260
260
  async changeFanSpeed(speed: number): Promise<boolean> {
261
- if (!this.speeds.includes(speed)) {
262
- const error = `Invalid speed: ${speed}. Must be one of: ${this.speeds.join(', ')}`;
263
- logger.error(`${error} for device: ${this.deviceName}`);
264
- throw new Error(error);
261
+ const speeds = this.config.levels ?? [];
262
+ if (!speeds.includes(speed)) {
263
+ logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
264
+ return false;
265
265
  }
266
266
 
267
267
  logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
@@ -297,10 +297,11 @@ export class VeSyncAirBypass extends VeSyncFan {
297
297
  * Set device mode
298
298
  */
299
299
  async setMode(mode: string): Promise<boolean> {
300
- if (!this.modes.includes(mode as any)) {
301
- const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
302
- logger.error(`${error} for device: ${this.deviceName}`);
303
- throw new Error(error);
300
+ const modeStr = mode.toLowerCase();
301
+ if (!this.modes.includes(modeStr as any)) {
302
+ logger.debug(`Invalid humidity mode used - ${mode}`);
303
+ logger.debug(`Proper modes for this device are - ${this.modes.join(', ')}`);
304
+ return false;
304
305
  }
305
306
 
306
307
  logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
@@ -313,9 +314,9 @@ export class VeSyncAirBypass extends VeSyncFan {
313
314
  configModule: this.configModule,
314
315
  payload: {
315
316
  data: {
316
- mode
317
+ workMode: this.deviceType.startsWith('Superior') && modeStr === 'auto' ? 'autoPro' : modeStr
317
318
  },
318
- method: 'setPurifierMode',
319
+ method: 'setHumidifierMode',
319
320
  source: 'APP'
320
321
  }
321
322
  },
@@ -324,7 +325,7 @@ export class VeSyncAirBypass extends VeSyncFan {
324
325
 
325
326
  const success = this.checkResponse([response, status], 'setMode');
326
327
  if (!success) {
327
- logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
328
+ logger.debug('Error setting humidity mode');
328
329
  }
329
330
  return success;
330
331
  }
@@ -601,6 +602,7 @@ export class VeSyncHumidifier extends VeSyncFan {
601
602
  protected readonly mistLevels: number[];
602
603
  protected readonly displayModes = ['on', 'off'] as const;
603
604
  protected readonly humidityRange = { min: 30, max: 80 };
605
+ protected readonly states = ['inactive', 'idle', 'humidifying'] as const;
604
606
 
605
607
  constructor(details: Record<string, any>, manager: VeSync) {
606
608
  super(details, manager);
@@ -619,6 +621,75 @@ export class VeSyncHumidifier extends VeSyncFan {
619
621
  logger.debug(`Initialized VeSyncHumidifier device: ${this.deviceName}`);
620
622
  }
621
623
 
624
+ /**
625
+ * Get current operational state
626
+ */
627
+ get currentState(): string {
628
+ if (this.deviceStatus === 'off') {
629
+ return 'inactive';
630
+ }
631
+ if (this.details.water_lacks) {
632
+ return 'idle';
633
+ }
634
+ return 'humidifying';
635
+ }
636
+
637
+ /**
638
+ * Get water level percentage
639
+ */
640
+ get waterLevel(): number {
641
+ return this.details.water_level ?? 0;
642
+ }
643
+
644
+ /**
645
+ * Get water tank status
646
+ */
647
+ get waterTankLifted(): boolean {
648
+ return this.details.water_tank_lifted ?? false;
649
+ }
650
+
651
+ /**
652
+ * Get oscillation state
653
+ */
654
+ get oscillationEnabled(): boolean {
655
+ return this.details.oscillation_enabled ?? false;
656
+ }
657
+
658
+ /**
659
+ * Set oscillation state
660
+ */
661
+ async setOscillation(enabled: boolean): Promise<boolean> {
662
+ if (!this.hasFeature('oscillation')) {
663
+ logger.debug('Oscillation not supported');
664
+ return false;
665
+ }
666
+
667
+ logger.debug(`Setting oscillation to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
668
+ const [response, status] = await this.callApi(
669
+ '/cloud/v2/deviceManaged/bypassV2',
670
+ 'post',
671
+ {
672
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
673
+ cid: this.cid,
674
+ configModule: this.configModule,
675
+ payload: {
676
+ data: {
677
+ oscillationEnabled: enabled
678
+ },
679
+ method: 'setSwing',
680
+ source: 'APP'
681
+ }
682
+ },
683
+ Helpers.reqHeaderBypass()
684
+ );
685
+
686
+ const success = this.checkResponse([response, status], 'setOscillation');
687
+ if (!success) {
688
+ logger.debug('Error setting oscillation');
689
+ }
690
+ return success;
691
+ }
692
+
622
693
  /**
623
694
  * Get device details
624
695
  */
@@ -656,12 +727,13 @@ export class VeSyncHumidifier extends VeSyncFan {
656
727
  humidity: result.humidity || 0,
657
728
  mist_level: result.mistLevel || 0,
658
729
  mist_virtual_level: result.virtualLevel || 0,
659
- warm_level: result.warmLevel || 0,
660
730
  water_lacks: result.waterLacks || false,
661
- humidity_high: result.humidityHigh || false,
731
+ water_level: result.waterLevel || 0,
662
732
  water_tank_lifted: result.waterTankLifted || false,
733
+ humidity_high: result.humidityHigh || false,
663
734
  display: result.display || false,
664
735
  automatic_stop: result.automaticStop || false,
736
+ oscillation_enabled: result.oscillationEnabled || false,
665
737
  configuration: result.configuration || {},
666
738
  connection_status: result.connectionStatus || null
667
739
  };
@@ -670,6 +742,37 @@ export class VeSyncHumidifier extends VeSyncFan {
670
742
  return success;
671
743
  }
672
744
 
745
+ /**
746
+ * Display device info
747
+ */
748
+ override display(): void {
749
+ super.display();
750
+ const info = [
751
+ ['Current State: ', this.currentState],
752
+ ['Water Level: ', this.waterLevel, '%'],
753
+ ['Water Tank: ', this.waterTankLifted ? 'Lifted' : 'Seated'],
754
+ ['Oscillation: ', this.oscillationEnabled ? 'On' : 'Off']
755
+ ];
756
+
757
+ for (const [key, value, unit = ''] of info) {
758
+ logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
759
+ }
760
+ }
761
+
762
+ /**
763
+ * Return JSON details
764
+ */
765
+ override displayJSON(): string {
766
+ const baseInfo = JSON.parse(super.displayJSON());
767
+ return JSON.stringify({
768
+ ...baseInfo,
769
+ 'Current State': this.currentState,
770
+ 'Water Level': this.waterLevel,
771
+ 'Water Tank': this.waterTankLifted ? 'Lifted' : 'Seated',
772
+ 'Oscillation': this.oscillationEnabled ? 'On' : 'Off'
773
+ }, null, 4);
774
+ }
775
+
673
776
  /**
674
777
  * Turn device on
675
778
  */
@@ -741,9 +844,9 @@ export class VeSyncHumidifier extends VeSyncFan {
741
844
  */
742
845
  async setMistLevel(level: number): Promise<boolean> {
743
846
  if (!this.mistLevels.includes(level)) {
744
- const error = `Invalid mist level: ${level}. Must be one of: ${this.mistLevels.join(', ')}`;
745
- logger.error(`${error} for device: ${this.deviceName}`);
746
- throw new Error(error);
847
+ logger.debug(`Invalid mist level: ${level} for device: ${this.deviceName}`);
848
+ logger.debug(`Valid levels for ${this.deviceType}: ${this.mistLevels.join(', ')}`);
849
+ return false;
747
850
  }
748
851
 
749
852
  logger.info(`Setting mist level to ${level} for device: ${this.deviceName}`);
@@ -756,9 +859,9 @@ export class VeSyncHumidifier extends VeSyncFan {
756
859
  configModule: this.configModule,
757
860
  payload: {
758
861
  data: {
759
- id: 0,
760
- level: level,
761
- type: 'mist'
862
+ levelIdx: 0,
863
+ virtualLevel: level,
864
+ levelType: 'mist'
762
865
  },
763
866
  method: 'setVirtualLevel',
764
867
  source: 'APP'
@@ -769,7 +872,7 @@ export class VeSyncHumidifier extends VeSyncFan {
769
872
 
770
873
  const success = this.checkResponse([response, status], 'setMistLevel');
771
874
  if (!success) {
772
- logger.error(`Failed to set mist level to ${level} for device: ${this.deviceName}`);
875
+ logger.debug('Error setting mist level');
773
876
  }
774
877
  return success;
775
878
  }
@@ -786,10 +889,11 @@ export class VeSyncHumidifier extends VeSyncFan {
786
889
  * Set device mode
787
890
  */
788
891
  async setMode(mode: string): Promise<boolean> {
789
- if (!this.modes.includes(mode as any)) {
790
- const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
791
- logger.error(`${error} for device: ${this.deviceName}`);
792
- throw new Error(error);
892
+ const modeStr = mode.toLowerCase();
893
+ if (!this.modes.includes(modeStr as any)) {
894
+ logger.debug(`Invalid humidity mode used - ${mode}`);
895
+ logger.debug(`Proper modes for this device are - ${this.modes.join(', ')}`);
896
+ return false;
793
897
  }
794
898
 
795
899
  logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
@@ -802,7 +906,7 @@ export class VeSyncHumidifier extends VeSyncFan {
802
906
  configModule: this.configModule,
803
907
  payload: {
804
908
  data: {
805
- mode
909
+ workMode: this.deviceType.startsWith('Superior') && modeStr === 'auto' ? 'autoPro' : modeStr
806
910
  },
807
911
  method: 'setHumidifierMode',
808
912
  source: 'APP'
@@ -813,7 +917,7 @@ export class VeSyncHumidifier extends VeSyncFan {
813
917
 
814
918
  const success = this.checkResponse([response, status], 'setMode');
815
919
  if (!success) {
816
- logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
920
+ logger.debug('Error setting humidity mode');
817
921
  }
818
922
  return success;
819
923
  }
@@ -1096,7 +1200,7 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1096
1200
  // Add warm humidifier specific fields
1097
1201
  this.details = {
1098
1202
  ...this.details,
1099
- warm_enabled: this.details.warm_level > 0,
1203
+ warm_enabled: Boolean(this.details.warm_level && this.details.warm_level > 0),
1100
1204
  drying_mode_enabled: this.details.autoDryingSwitch === 1,
1101
1205
  drying_mode_state: this.details.dryingMode || '',
1102
1206
  drying_mode_level: this.details.dryingLevel || 0,
@@ -1118,10 +1222,11 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1118
1222
  * Set device mode
1119
1223
  */
1120
1224
  async setMode(mode: string): Promise<boolean> {
1121
- if (!this.modes.includes(mode as any)) {
1122
- const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
1123
- logger.error(`${error} for device: ${this.deviceName}`);
1124
- throw new Error(error);
1225
+ const modeStr = mode.toLowerCase();
1226
+ if (!this.modes.includes(modeStr as any)) {
1227
+ logger.debug(`Invalid humidity mode used - ${mode}`);
1228
+ logger.debug(`Proper modes for this device are - ${this.modes.join(', ')}`);
1229
+ return false;
1125
1230
  }
1126
1231
 
1127
1232
  logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
@@ -1134,7 +1239,7 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1134
1239
  configModule: this.configModule,
1135
1240
  payload: {
1136
1241
  data: {
1137
- mode
1242
+ workMode: this.deviceType.startsWith('Superior') && modeStr === 'auto' ? 'autoPro' : modeStr
1138
1243
  },
1139
1244
  method: 'setHumidifierMode',
1140
1245
  source: 'APP'
@@ -1144,10 +1249,8 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1144
1249
  );
1145
1250
 
1146
1251
  const success = this.checkResponse([response, status], 'setMode');
1147
- if (success) {
1148
- this.details.mode = mode;
1149
- } else {
1150
- logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
1252
+ if (!success) {
1253
+ logger.debug('Error setting humidity mode');
1151
1254
  }
1152
1255
  return success;
1153
1256
  }
@@ -1157,15 +1260,14 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1157
1260
  */
1158
1261
  async setWarmLevel(level: number): Promise<boolean> {
1159
1262
  if (!this.hasFeature('warm')) {
1160
- const error = 'Warm mist control not supported';
1161
- logger.error(`${error} for device: ${this.deviceName}`);
1162
- throw new Error(error);
1263
+ logger.debug('Warm mist control not supported');
1264
+ return false;
1163
1265
  }
1164
1266
 
1165
1267
  if (!this.warmLevels.includes(level)) {
1166
- const error = `Invalid warm level: ${level}. Must be one of: ${this.warmLevels.join(', ')}`;
1167
- logger.error(`${error} for device: ${this.deviceName}`);
1168
- throw new Error(error);
1268
+ logger.debug(`Invalid warm level: ${level} for device: ${this.deviceName}`);
1269
+ logger.debug(`Valid levels for ${this.deviceType}: ${this.warmLevels.join(', ')}`);
1270
+ return false;
1169
1271
  }
1170
1272
 
1171
1273
  logger.debug(`Setting warm level to ${level} for device: ${this.deviceName}`);
@@ -1292,7 +1394,10 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1292
1394
 
1293
1395
  protected buildConfigDict(configDict: Record<string, any>): void {
1294
1396
  if (configDict) {
1295
- this.config = configDict;
1397
+ this.config = {
1398
+ ...this.config, // Preserve existing config
1399
+ ...configDict // Merge in new config
1400
+ };
1296
1401
  }
1297
1402
  }
1298
1403
 
@@ -1328,6 +1433,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1328
1433
  const body = {
1329
1434
  ...Helpers.reqBody(this.manager, 'bypassV2'),
1330
1435
  cid: this.cid,
1436
+ deviceId: this.cid, // Add deviceId to match YAML spec
1331
1437
  configModule: this.configModule,
1332
1438
  payload: {
1333
1439
  method,
@@ -1467,35 +1573,32 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1467
1573
  * Change fan speed
1468
1574
  */
1469
1575
  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
- }
1576
+ const speeds = this.config.levels ?? [];
1577
+ if (!speeds.includes(speed)) {
1578
+ logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
1579
+ return false;
1485
1580
  }
1486
1581
 
1487
- const [head, body] = this.buildApiDict('setLevel');
1488
- body.payload.data = {
1489
- levelIdx: 0,
1490
- manualSpeedLevel: speed,
1491
- levelType: 'wind'
1492
- };
1493
-
1582
+ logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
1494
1583
  const [response, status] = await this.callApi(
1495
1584
  '/cloud/v2/deviceManaged/bypassV2',
1496
1585
  'post',
1497
- body,
1498
- head
1586
+ {
1587
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
1588
+ cid: this.cid,
1589
+ configModule: this.configModule,
1590
+ payload: {
1591
+ data: {
1592
+ id: 0,
1593
+ level: speed,
1594
+ mode: 'manual',
1595
+ type: 'wind'
1596
+ },
1597
+ method: 'setLevel',
1598
+ source: 'APP'
1599
+ }
1600
+ },
1601
+ Helpers.reqHeaderBypass()
1499
1602
  );
1500
1603
 
1501
1604
  const success = this.checkResponse([response, status], 'changeFanSpeed');
@@ -1757,13 +1860,14 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1757
1860
  const details: Record<string, string> = {
1758
1861
  ...baseInfo,
1759
1862
  'Mode': this.mode,
1760
- 'Filter Life': this.details.filter_life.toString(),
1863
+ 'Filter Life': (this.details.filter_life ?? 0).toString(),
1761
1864
  '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()
1865
+ 'Display On': this.details.display?.toString() ?? 'false',
1866
+ 'Child Lock': (this.details.child_lock ?? false).toString(),
1867
+ 'Night Light': (this.details.night_light ?? '').toString(),
1868
+ 'Display Set On': (this.details.screen_switch ?? false).toString(),
1869
+ 'Light Detection Enabled': (this.details.light_detection_switch ?? false).toString(),
1870
+ 'Environment Light State': (this.details.environment_light_state ?? false).toString()
1767
1871
  };
1768
1872
 
1769
1873
  if (this.hasFeature('air_quality')) {
@@ -1780,7 +1884,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1780
1884
 
1781
1885
  for (const [key, value] of Object.entries(everestKeys)) {
1782
1886
  if (key in this.details) {
1783
- details[value] = this.details[key].toString();
1887
+ details[value] = this.details[key]?.toString() ?? '';
1784
1888
  }
1785
1889
  }
1786
1890
 
@@ -1820,7 +1924,6 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1820
1924
  */
1821
1925
  export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1822
1926
  protected readonly towerModes = ['normal', 'advancedSleep', 'off'] as const;
1823
- protected readonly speeds = Array.from({length: 12}, (_, i) => i + 1);
1824
1927
 
1825
1928
  constructor(details: Record<string, any>, manager: VeSync) {
1826
1929
  super(details, manager);
@@ -1851,6 +1954,7 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1851
1954
  const body = {
1852
1955
  ...Helpers.reqBody(this.manager, 'bypassV2'),
1853
1956
  cid: this.cid,
1957
+ deviceId: this.cid, // Add deviceId to match YAML spec
1854
1958
  configModule: this.configModule,
1855
1959
  payload: {
1856
1960
  method,
@@ -1899,8 +2003,6 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1899
2003
  screenSwitch: deviceData.screenSwitch,
1900
2004
  oscillationState: deviceData.oscillationState,
1901
2005
  oscillationSwitch: deviceData.oscillationSwitch,
1902
- muteState: deviceData.muteState,
1903
- muteSwitch: deviceData.muteSwitch,
1904
2006
  timerRemain: deviceData.timerRemain,
1905
2007
  temperature: deviceData.temperature || 0,
1906
2008
  humidity: deviceData.humidity || 0,
@@ -1966,13 +2068,12 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1966
2068
  * Set tower fan mode
1967
2069
  */
1968
2070
  async mode_toggle(mode: string): Promise<boolean> {
1969
- const validModes = this.towerModes.map(m => m.toLowerCase());
1970
- if (!validModes.includes(mode.toLowerCase())) {
1971
- logger.debug(`Invalid tower fan mode used - ${mode}`);
2071
+ if (!this.towerModes.includes(mode as any)) {
2072
+ logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.towerModes.join(', ')}`);
1972
2073
  return false;
1973
2074
  }
1974
2075
 
1975
- if (mode.toLowerCase() === 'off') {
2076
+ if (mode === 'off') {
1976
2077
  return this.turnOff();
1977
2078
  }
1978
2079
 
@@ -1993,7 +2094,7 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1993
2094
  if (success) {
1994
2095
  this.details.workMode = mode;
1995
2096
  } else {
1996
- logger.debug('Error setting tower fan mode');
2097
+ logger.error(`Failed to set ${mode} mode for device: ${this.deviceName}`);
1997
2098
  }
1998
2099
  return success;
1999
2100
  }
@@ -2002,8 +2103,9 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
2002
2103
  * Change fan speed
2003
2104
  */
2004
2105
  async changeFanSpeed(speed: number): Promise<boolean> {
2005
- if (!this.speeds.includes(speed)) {
2006
- logger.debug(`Invalid speed: ${speed}. Must be one of: ${this.speeds.join(', ')}`);
2106
+ const speeds = this.config.levels ?? [];
2107
+ if (!speeds.includes(speed)) {
2108
+ logger.debug(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')}`);
2007
2109
  return false;
2008
2110
  }
2009
2111