tsvesync 1.0.35 → 1.0.36

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.
@@ -93,6 +93,22 @@ export declare class VeSyncHumidifier extends VeSyncFan {
93
93
  * Get device details
94
94
  */
95
95
  getDetails(): Promise<Boolean>;
96
+ /**
97
+ * Turn device on
98
+ */
99
+ turnOn(): Promise<boolean>;
100
+ /**
101
+ * Turn device off
102
+ */
103
+ turnOff(): Promise<boolean>;
104
+ /**
105
+ * Get model-specific toggle payload
106
+ */
107
+ protected getTogglePayload(enabled: boolean): Record<string, any>;
108
+ /**
109
+ * Toggle device power
110
+ */
111
+ toggleSwitch(enabled: boolean): Promise<boolean>;
96
112
  /**
97
113
  * Set mist level
98
114
  */
@@ -105,14 +121,6 @@ export declare class VeSyncHumidifier extends VeSyncFan {
105
121
  * Set device mode
106
122
  */
107
123
  setMode(mode: string): Promise<boolean>;
108
- /**
109
- * Turn device on
110
- */
111
- turnOn(): Promise<boolean>;
112
- /**
113
- * Turn device off
114
- */
115
- turnOff(): Promise<boolean>;
116
124
  /**
117
125
  * Set target humidity
118
126
  */
@@ -160,6 +168,10 @@ export declare class VeSyncHumidifier extends VeSyncFan {
160
168
  export declare class VeSyncWarmHumidifier extends VeSyncHumidifier {
161
169
  protected readonly warmLevels: number[];
162
170
  constructor(details: Record<string, any>, manager: VeSync);
171
+ /**
172
+ * Get device details
173
+ */
174
+ getDetails(): Promise<Boolean>;
163
175
  /**
164
176
  * Change fan speed - Implemented to satisfy interface but redirects to setMistLevel
165
177
  */
@@ -176,5 +188,25 @@ export declare class VeSyncWarmHumidifier extends VeSyncHumidifier {
176
188
  * Set drying mode enabled
177
189
  */
178
190
  setDryingModeEnabled(enabled: boolean): Promise<boolean>;
191
+ /**
192
+ * Get warm mist enabled status
193
+ */
194
+ get warmMistEnabled(): boolean;
195
+ /**
196
+ * Get drying mode enabled status
197
+ */
198
+ get dryingModeEnabled(): boolean;
199
+ /**
200
+ * Get drying mode state
201
+ */
202
+ get dryingModeState(): string;
203
+ /**
204
+ * Get drying mode level
205
+ */
206
+ get dryingModeLevel(): number;
207
+ /**
208
+ * Get drying mode seconds remaining
209
+ */
210
+ get dryingModeSecondsRemaining(): number;
179
211
  }
180
212
  export declare const fanModules: Record<string, new (details: Record<string, any>, manager: VeSync) => VeSyncFan>;
@@ -23,8 +23,45 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
23
23
  * Get device details
24
24
  */
25
25
  async getDetails() {
26
- var _a;
26
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
27
27
  logger_1.logger.debug(`Getting details for device: ${this.deviceName}`);
28
+ // Special handling for LV series
29
+ if (this.deviceType.startsWith('LV-')) {
30
+ const [response, status] = await this.callApi('/131airPurifier/v1/device/devicedetail', 'post', {
31
+ acceptLanguage: 'en',
32
+ accountID: this.manager.accountId,
33
+ appVersion: '2.8.6',
34
+ method: 'devicedetail',
35
+ mobileId: '1234567890123456',
36
+ phoneBrand: 'SM N9005',
37
+ phoneOS: 'Android',
38
+ timeZone: this.manager.timeZone,
39
+ token: this.manager.token,
40
+ uuid: this.uuid
41
+ }, {
42
+ 'accept-language': 'en',
43
+ 'accountId': this.manager.accountId,
44
+ 'appVersion': '2.8.6',
45
+ 'content-type': 'application/json',
46
+ 'tk': this.manager.token,
47
+ 'tz': this.manager.timeZone
48
+ });
49
+ const success = this.checkResponse([response, status], 'getDetails');
50
+ if (success && response) {
51
+ this.deviceStatus = ((_b = (_a = response.result) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.deviceStatus) === 'on' ? 'on' : 'off';
52
+ this.details = {
53
+ mode: ((_d = (_c = response.result) === null || _c === void 0 ? void 0 : _c.result) === null || _d === void 0 ? void 0 : _d.mode) || '',
54
+ speed: ((_f = (_e = response.result) === null || _e === void 0 ? void 0 : _e.result) === null || _f === void 0 ? void 0 : _f.level) || 0,
55
+ filterLife: ((_h = (_g = response.result) === null || _g === void 0 ? void 0 : _g.result) === null || _h === void 0 ? void 0 : _h.filterLife) || 0,
56
+ screenStatus: ((_k = (_j = response.result) === null || _j === void 0 ? void 0 : _j.result) === null || _k === void 0 ? void 0 : _k.display) || false,
57
+ childLock: ((_m = (_l = response.result) === null || _l === void 0 ? void 0 : _l.result) === null || _m === void 0 ? void 0 : _m.childLock) || false,
58
+ airQuality: ((_p = (_o = response.result) === null || _o === void 0 ? void 0 : _o.result) === null || _p === void 0 ? void 0 : _p.airQuality) || 0
59
+ };
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+ // Original implementation for other devices
28
65
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
29
66
  ...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
30
67
  cid: this.cid,
@@ -37,11 +74,9 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
37
74
  }, helpers_1.Helpers.reqHeaderBypass());
38
75
  const success = this.checkResponse([response, status], 'getDetails');
39
76
  if (success) {
40
- const result = (_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.result;
77
+ const result = (_q = response === null || response === void 0 ? void 0 : response.result) === null || _q === void 0 ? void 0 : _q.result;
41
78
  if (result) {
42
- // Update device status
43
79
  this.deviceStatus = result.enabled ? 'on' : 'off';
44
- // Store device settings
45
80
  this.details = {
46
81
  mode: result.mode || '',
47
82
  speed: result.level || 0,
@@ -50,7 +85,6 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
50
85
  childLock: result.child_lock || false,
51
86
  airQuality: result.air_quality || 0
52
87
  };
53
- // Store configuration if available
54
88
  if (result.configuration) {
55
89
  this.config = result.configuration;
56
90
  }
@@ -64,6 +98,30 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
64
98
  */
65
99
  async turnOn() {
66
100
  logger_1.logger.info(`Turning on device: ${this.deviceName}`);
101
+ // Special handling for LV series
102
+ if (this.deviceType.startsWith('LV-')) {
103
+ const [response, status] = await this.callApi('/131airPurifier/v1/device/deviceStatus', 'put', {
104
+ acceptLanguage: 'en',
105
+ accountID: this.manager.accountId,
106
+ status: 'on',
107
+ timeZone: this.manager.timeZone,
108
+ token: this.manager.token,
109
+ uuid: this.uuid
110
+ }, {
111
+ 'accept-language': 'en',
112
+ 'accountId': this.manager.accountId,
113
+ 'appVersion': '2.8.6',
114
+ 'content-type': 'application/json',
115
+ 'tk': this.manager.token,
116
+ 'tz': this.manager.timeZone
117
+ });
118
+ const success = this.checkResponse([response, status], 'turnOn');
119
+ if (!success) {
120
+ logger_1.logger.error(`Failed to turn on device: ${this.deviceName}`);
121
+ }
122
+ return success;
123
+ }
124
+ // Original implementation for other devices
67
125
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
68
126
  ...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
69
127
  cid: this.cid,
@@ -88,6 +146,30 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
88
146
  */
89
147
  async turnOff() {
90
148
  logger_1.logger.info(`Turning off device: ${this.deviceName}`);
149
+ // Special handling for LV series
150
+ if (this.deviceType.startsWith('LV-')) {
151
+ const [response, status] = await this.callApi('/131airPurifier/v1/device/deviceStatus', 'put', {
152
+ acceptLanguage: 'en',
153
+ accountID: this.manager.accountId,
154
+ status: 'off',
155
+ timeZone: this.manager.timeZone,
156
+ token: this.manager.token,
157
+ uuid: this.uuid
158
+ }, {
159
+ 'accept-language': 'en',
160
+ 'accountId': this.manager.accountId,
161
+ 'appVersion': '2.8.6',
162
+ 'content-type': 'application/json',
163
+ 'tk': this.manager.token,
164
+ 'tz': this.manager.timeZone
165
+ });
166
+ const success = this.checkResponse([response, status], 'turnOff');
167
+ if (!success) {
168
+ logger_1.logger.error(`Failed to turn off device: ${this.deviceName}`);
169
+ }
170
+ return success;
171
+ }
172
+ // Original implementation for other devices
91
173
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
92
174
  ...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
93
175
  cid: this.cid,
@@ -421,11 +503,91 @@ class VeSyncHumidifier extends vesyncFan_1.VeSyncFan {
421
503
  }, helpers_1.Helpers.reqHeaderBypass());
422
504
  const success = this.checkResponse([response, status], 'getDetails');
423
505
  if (success && ((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.result)) {
424
- this.details = response.result.result;
506
+ const result = response.result.result;
507
+ // Handle model-specific status fields
508
+ if (this.deviceType.startsWith('Classic') || this.deviceType === 'Dual200S') {
509
+ this.deviceStatus = result.enabled ? 'on' : 'off';
510
+ }
511
+ else {
512
+ this.deviceStatus = result.powerSwitch === 1 ? 'on' : 'off';
513
+ }
514
+ this.details = {
515
+ mode: result.mode || '',
516
+ humidity: result.humidity || 0,
517
+ mist_level: result.mistLevel || 0,
518
+ mist_virtual_level: result.virtualLevel || 0,
519
+ warm_level: result.warmLevel || 0,
520
+ water_lacks: result.waterLacks || false,
521
+ humidity_high: result.humidityHigh || false,
522
+ water_tank_lifted: result.waterTankLifted || false,
523
+ display: result.display || false,
524
+ automatic_stop: result.automaticStop || false,
525
+ configuration: result.configuration || {},
526
+ connection_status: result.connectionStatus || null
527
+ };
425
528
  logger_1.logger.debug(`Successfully got details for device: ${this.deviceName}`);
426
529
  }
427
530
  return success;
428
531
  }
532
+ /**
533
+ * Turn device on
534
+ */
535
+ async turnOn() {
536
+ return this.toggleSwitch(true);
537
+ }
538
+ /**
539
+ * Turn device off
540
+ */
541
+ async turnOff() {
542
+ return this.toggleSwitch(false);
543
+ }
544
+ /**
545
+ * Get model-specific toggle payload
546
+ */
547
+ getTogglePayload(enabled) {
548
+ // Model-specific toggle payload based on PyVeSync implementation
549
+ if (this.deviceType.startsWith('Classic') || this.deviceType === 'Dual200S') {
550
+ return {
551
+ enabled: enabled,
552
+ id: 0
553
+ };
554
+ }
555
+ else if (this.deviceType.startsWith('Superior')) {
556
+ return {
557
+ powerSwitch: enabled ? 1 : 0,
558
+ switchIdx: 0
559
+ };
560
+ }
561
+ else {
562
+ return {
563
+ powerSwitch: enabled ? 1 : 0
564
+ };
565
+ }
566
+ }
567
+ /**
568
+ * Toggle device power
569
+ */
570
+ async toggleSwitch(enabled) {
571
+ logger_1.logger.info(`Setting device power to ${enabled ? 'on' : 'off'}: ${this.deviceName}`);
572
+ const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
573
+ ...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
574
+ cid: this.cid,
575
+ configModule: this.configModule,
576
+ payload: {
577
+ data: this.getTogglePayload(enabled),
578
+ method: 'setSwitch',
579
+ source: 'APP'
580
+ }
581
+ }, helpers_1.Helpers.reqHeaderBypass());
582
+ const success = this.checkResponse([response, status], 'toggleSwitch');
583
+ if (success) {
584
+ this.deviceStatus = enabled ? 'on' : 'off';
585
+ }
586
+ else {
587
+ logger_1.logger.error(`Failed to set device power to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
588
+ }
589
+ return success;
590
+ }
429
591
  /**
430
592
  * Set mist level
431
593
  */
@@ -491,54 +653,6 @@ class VeSyncHumidifier extends vesyncFan_1.VeSyncFan {
491
653
  }
492
654
  return success;
493
655
  }
494
- /**
495
- * Turn device on
496
- */
497
- async turnOn() {
498
- logger_1.logger.info(`Turning on device: ${this.deviceName}`);
499
- const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
500
- ...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
501
- cid: this.cid,
502
- configModule: this.configModule,
503
- payload: {
504
- data: {
505
- enabled: true,
506
- id: 0
507
- },
508
- method: 'setSwitch',
509
- source: 'APP'
510
- }
511
- }, helpers_1.Helpers.reqHeaderBypass());
512
- const success = this.checkResponse([response, status], 'turnOn');
513
- if (!success) {
514
- logger_1.logger.error(`Failed to turn on device: ${this.deviceName}`);
515
- }
516
- return success;
517
- }
518
- /**
519
- * Turn device off
520
- */
521
- async turnOff() {
522
- logger_1.logger.info(`Turning off device: ${this.deviceName}`);
523
- const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
524
- ...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
525
- cid: this.cid,
526
- configModule: this.configModule,
527
- payload: {
528
- data: {
529
- enabled: false,
530
- id: 0
531
- },
532
- method: 'setSwitch',
533
- source: 'APP'
534
- }
535
- }, helpers_1.Helpers.reqHeaderBypass());
536
- const success = this.checkResponse([response, status], 'turnOff');
537
- if (!success) {
538
- logger_1.logger.error(`Failed to turn off device: ${this.deviceName}`);
539
- }
540
- return success;
541
- }
542
656
  /**
543
657
  * Set target humidity
544
658
  */
@@ -756,6 +870,24 @@ class VeSyncWarmHumidifier extends VeSyncHumidifier {
756
870
  this.warmLevels = [1, 2, 3];
757
871
  logger_1.logger.debug(`Initialized VeSyncWarmHumidifier device: ${this.deviceName}`);
758
872
  }
873
+ /**
874
+ * Get device details
875
+ */
876
+ async getDetails() {
877
+ const success = await super.getDetails();
878
+ if (success && this.details) {
879
+ // Add warm humidifier specific fields
880
+ this.details = {
881
+ ...this.details,
882
+ warm_enabled: this.details.warm_level > 0,
883
+ drying_mode_enabled: this.details.autoDryingSwitch === 1,
884
+ drying_mode_state: this.details.dryingMode || '',
885
+ drying_mode_level: this.details.dryingLevel || 0,
886
+ drying_mode_seconds_remaining: this.details.dryingRemainTime || 0
887
+ };
888
+ }
889
+ return success;
890
+ }
759
891
  /**
760
892
  * Change fan speed - Implemented to satisfy interface but redirects to setMistLevel
761
893
  */
@@ -786,7 +918,10 @@ class VeSyncWarmHumidifier extends VeSyncHumidifier {
786
918
  }
787
919
  }, helpers_1.Helpers.reqHeaderBypass());
788
920
  const success = this.checkResponse([response, status], 'setMode');
789
- if (!success) {
921
+ if (success) {
922
+ this.details.mode = mode;
923
+ }
924
+ else {
790
925
  logger_1.logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
791
926
  }
792
927
  return success;
@@ -819,7 +954,11 @@ class VeSyncWarmHumidifier extends VeSyncHumidifier {
819
954
  }
820
955
  }, helpers_1.Helpers.reqHeaderBypass());
821
956
  const success = this.checkResponse([response, status], 'setWarmLevel');
822
- if (!success) {
957
+ if (success) {
958
+ this.details.warm_level = level;
959
+ this.details.warm_enabled = level > 0;
960
+ }
961
+ else {
823
962
  logger_1.logger.error(`Failed to set warm level to ${level} for device: ${this.deviceName}`);
824
963
  }
825
964
  return success;
@@ -847,11 +986,45 @@ class VeSyncWarmHumidifier extends VeSyncHumidifier {
847
986
  }
848
987
  }, helpers_1.Helpers.reqHeaderBypass());
849
988
  const success = this.checkResponse([response, status], 'setDryingModeEnabled');
850
- if (!success) {
989
+ if (success) {
990
+ this.details.drying_mode_enabled = enabled;
991
+ this.details.autoDryingSwitch = enabled ? 1 : 0;
992
+ }
993
+ else {
851
994
  logger_1.logger.error(`Failed to set drying mode to ${enabled} for device: ${this.deviceName}`);
852
995
  }
853
996
  return success;
854
997
  }
998
+ /**
999
+ * Get warm mist enabled status
1000
+ */
1001
+ get warmMistEnabled() {
1002
+ return this.details.warm_enabled || false;
1003
+ }
1004
+ /**
1005
+ * Get drying mode enabled status
1006
+ */
1007
+ get dryingModeEnabled() {
1008
+ return this.details.drying_mode_enabled || false;
1009
+ }
1010
+ /**
1011
+ * Get drying mode state
1012
+ */
1013
+ get dryingModeState() {
1014
+ return this.details.drying_mode_state || '';
1015
+ }
1016
+ /**
1017
+ * Get drying mode level
1018
+ */
1019
+ get dryingModeLevel() {
1020
+ return this.details.drying_mode_level || 0;
1021
+ }
1022
+ /**
1023
+ * Get drying mode seconds remaining
1024
+ */
1025
+ get dryingModeSecondsRemaining() {
1026
+ return this.details.drying_mode_seconds_remaining || 0;
1027
+ }
855
1028
  }
856
1029
  exports.VeSyncWarmHumidifier = VeSyncWarmHumidifier;
857
1030
  // Export fan modules dictionary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "description": "A TypeScript library for interacting with VeSync smart home devices",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,6 +26,51 @@ export class VeSyncAirBypass extends VeSyncFan {
26
26
  */
27
27
  async getDetails(): Promise<Boolean> {
28
28
  logger.debug(`Getting details for device: ${this.deviceName}`);
29
+
30
+ // Special handling for LV series
31
+ if (this.deviceType.startsWith('LV-')) {
32
+ const [response, status] = await this.callApi(
33
+ '/131airPurifier/v1/device/devicedetail',
34
+ 'post',
35
+ {
36
+ acceptLanguage: 'en',
37
+ accountID: this.manager.accountId!,
38
+ appVersion: '2.8.6',
39
+ method: 'devicedetail',
40
+ mobileId: '1234567890123456',
41
+ phoneBrand: 'SM N9005',
42
+ phoneOS: 'Android',
43
+ timeZone: this.manager.timeZone!,
44
+ token: this.manager.token!,
45
+ uuid: this.uuid
46
+ },
47
+ {
48
+ 'accept-language': 'en',
49
+ 'accountId': this.manager.accountId!,
50
+ 'appVersion': '2.8.6',
51
+ 'content-type': 'application/json',
52
+ 'tk': this.manager.token!,
53
+ 'tz': this.manager.timeZone!
54
+ }
55
+ );
56
+
57
+ const success = this.checkResponse([response, status], 'getDetails');
58
+ if (success && response) {
59
+ this.deviceStatus = response.result?.result?.deviceStatus === 'on' ? 'on' : 'off';
60
+ this.details = {
61
+ mode: response.result?.result?.mode || '',
62
+ speed: response.result?.result?.level || 0,
63
+ filterLife: response.result?.result?.filterLife || 0,
64
+ screenStatus: response.result?.result?.display || false,
65
+ childLock: response.result?.result?.childLock || false,
66
+ airQuality: response.result?.result?.airQuality || 0
67
+ };
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+
73
+ // Original implementation for other devices
29
74
  const [response, status] = await this.callApi(
30
75
  '/cloud/v2/deviceManaged/bypassV2',
31
76
  'post',
@@ -46,10 +91,7 @@ export class VeSyncAirBypass extends VeSyncFan {
46
91
  if (success) {
47
92
  const result = response?.result?.result;
48
93
  if (result) {
49
- // Update device status
50
94
  this.deviceStatus = result.enabled ? 'on' : 'off';
51
-
52
- // Store device settings
53
95
  this.details = {
54
96
  mode: result.mode || '',
55
97
  speed: result.level || 0,
@@ -59,7 +101,6 @@ export class VeSyncAirBypass extends VeSyncFan {
59
101
  airQuality: result.air_quality || 0
60
102
  };
61
103
 
62
- // Store configuration if available
63
104
  if (result.configuration) {
64
105
  this.config = result.configuration;
65
106
  }
@@ -74,6 +115,38 @@ export class VeSyncAirBypass extends VeSyncFan {
74
115
  */
75
116
  async turnOn(): Promise<boolean> {
76
117
  logger.info(`Turning on device: ${this.deviceName}`);
118
+
119
+ // Special handling for LV series
120
+ if (this.deviceType.startsWith('LV-')) {
121
+ const [response, status] = await this.callApi(
122
+ '/131airPurifier/v1/device/deviceStatus',
123
+ 'put',
124
+ {
125
+ acceptLanguage: 'en',
126
+ accountID: this.manager.accountId!,
127
+ status: 'on',
128
+ timeZone: this.manager.timeZone!,
129
+ token: this.manager.token!,
130
+ uuid: this.uuid
131
+ },
132
+ {
133
+ 'accept-language': 'en',
134
+ 'accountId': this.manager.accountId!,
135
+ 'appVersion': '2.8.6',
136
+ 'content-type': 'application/json',
137
+ 'tk': this.manager.token!,
138
+ 'tz': this.manager.timeZone!
139
+ }
140
+ );
141
+
142
+ const success = this.checkResponse([response, status], 'turnOn');
143
+ if (!success) {
144
+ logger.error(`Failed to turn on device: ${this.deviceName}`);
145
+ }
146
+ return success;
147
+ }
148
+
149
+ // Original implementation for other devices
77
150
  const [response, status] = await this.callApi(
78
151
  '/cloud/v2/deviceManaged/bypassV2',
79
152
  'post',
@@ -105,6 +178,38 @@ export class VeSyncAirBypass extends VeSyncFan {
105
178
  */
106
179
  async turnOff(): Promise<boolean> {
107
180
  logger.info(`Turning off device: ${this.deviceName}`);
181
+
182
+ // Special handling for LV series
183
+ if (this.deviceType.startsWith('LV-')) {
184
+ const [response, status] = await this.callApi(
185
+ '/131airPurifier/v1/device/deviceStatus',
186
+ 'put',
187
+ {
188
+ acceptLanguage: 'en',
189
+ accountID: this.manager.accountId!,
190
+ status: 'off',
191
+ timeZone: this.manager.timeZone!,
192
+ token: this.manager.token!,
193
+ uuid: this.uuid
194
+ },
195
+ {
196
+ 'accept-language': 'en',
197
+ 'accountId': this.manager.accountId!,
198
+ 'appVersion': '2.8.6',
199
+ 'content-type': 'application/json',
200
+ 'tk': this.manager.token!,
201
+ 'tz': this.manager.timeZone!
202
+ }
203
+ );
204
+
205
+ const success = this.checkResponse([response, status], 'turnOff');
206
+ if (!success) {
207
+ logger.error(`Failed to turn off device: ${this.deviceName}`);
208
+ }
209
+ return success;
210
+ }
211
+
212
+ // Original implementation for other devices
108
213
  const [response, status] = await this.callApi(
109
214
  '/cloud/v2/deviceManaged/bypassV2',
110
215
  'post',
@@ -519,12 +624,100 @@ export class VeSyncHumidifier extends VeSyncFan {
519
624
 
520
625
  const success = this.checkResponse([response, status], 'getDetails');
521
626
  if (success && response?.result?.result) {
522
- this.details = response.result.result;
627
+ const result = response.result.result;
628
+
629
+ // Handle model-specific status fields
630
+ if (this.deviceType.startsWith('Classic') || this.deviceType === 'Dual200S') {
631
+ this.deviceStatus = result.enabled ? 'on' : 'off';
632
+ } else {
633
+ this.deviceStatus = result.powerSwitch === 1 ? 'on' : 'off';
634
+ }
635
+
636
+ this.details = {
637
+ mode: result.mode || '',
638
+ humidity: result.humidity || 0,
639
+ mist_level: result.mistLevel || 0,
640
+ mist_virtual_level: result.virtualLevel || 0,
641
+ warm_level: result.warmLevel || 0,
642
+ water_lacks: result.waterLacks || false,
643
+ humidity_high: result.humidityHigh || false,
644
+ water_tank_lifted: result.waterTankLifted || false,
645
+ display: result.display || false,
646
+ automatic_stop: result.automaticStop || false,
647
+ configuration: result.configuration || {},
648
+ connection_status: result.connectionStatus || null
649
+ };
523
650
  logger.debug(`Successfully got details for device: ${this.deviceName}`);
524
651
  }
525
652
  return success;
526
653
  }
527
654
 
655
+ /**
656
+ * Turn device on
657
+ */
658
+ async turnOn(): Promise<boolean> {
659
+ return this.toggleSwitch(true);
660
+ }
661
+
662
+ /**
663
+ * Turn device off
664
+ */
665
+ async turnOff(): Promise<boolean> {
666
+ return this.toggleSwitch(false);
667
+ }
668
+
669
+ /**
670
+ * Get model-specific toggle payload
671
+ */
672
+ protected getTogglePayload(enabled: boolean): Record<string, any> {
673
+ // Model-specific toggle payload based on PyVeSync implementation
674
+ if (this.deviceType.startsWith('Classic') || this.deviceType === 'Dual200S') {
675
+ return {
676
+ enabled: enabled,
677
+ id: 0
678
+ };
679
+ } else if (this.deviceType.startsWith('Superior')) {
680
+ return {
681
+ powerSwitch: enabled ? 1 : 0,
682
+ switchIdx: 0
683
+ };
684
+ } else {
685
+ return {
686
+ powerSwitch: enabled ? 1 : 0
687
+ };
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Toggle device power
693
+ */
694
+ async toggleSwitch(enabled: boolean): Promise<boolean> {
695
+ logger.info(`Setting device power to ${enabled ? 'on' : 'off'}: ${this.deviceName}`);
696
+ const [response, status] = await this.callApi(
697
+ '/cloud/v2/deviceManaged/bypassV2',
698
+ 'post',
699
+ {
700
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
701
+ cid: this.cid,
702
+ configModule: this.configModule,
703
+ payload: {
704
+ data: this.getTogglePayload(enabled),
705
+ method: 'setSwitch',
706
+ source: 'APP'
707
+ }
708
+ },
709
+ Helpers.reqHeaderBypass()
710
+ );
711
+
712
+ const success = this.checkResponse([response, status], 'toggleSwitch');
713
+ if (success) {
714
+ this.deviceStatus = enabled ? 'on' : 'off';
715
+ } else {
716
+ logger.error(`Failed to set device power to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
717
+ }
718
+ return success;
719
+ }
720
+
528
721
  /**
529
722
  * Set mist level
530
723
  */
@@ -607,68 +800,6 @@ export class VeSyncHumidifier extends VeSyncFan {
607
800
  return success;
608
801
  }
609
802
 
610
- /**
611
- * Turn device on
612
- */
613
- async turnOn(): Promise<boolean> {
614
- logger.info(`Turning on device: ${this.deviceName}`);
615
- const [response, status] = await this.callApi(
616
- '/cloud/v2/deviceManaged/bypassV2',
617
- 'post',
618
- {
619
- ...Helpers.reqBody(this.manager, 'bypassV2'),
620
- cid: this.cid,
621
- configModule: this.configModule,
622
- payload: {
623
- data: {
624
- enabled: true,
625
- id: 0
626
- },
627
- method: 'setSwitch',
628
- source: 'APP'
629
- }
630
- },
631
- Helpers.reqHeaderBypass()
632
- );
633
-
634
- const success = this.checkResponse([response, status], 'turnOn');
635
- if (!success) {
636
- logger.error(`Failed to turn on device: ${this.deviceName}`);
637
- }
638
- return success;
639
- }
640
-
641
- /**
642
- * Turn device off
643
- */
644
- async turnOff(): Promise<boolean> {
645
- logger.info(`Turning off device: ${this.deviceName}`);
646
- const [response, status] = await this.callApi(
647
- '/cloud/v2/deviceManaged/bypassV2',
648
- 'post',
649
- {
650
- ...Helpers.reqBody(this.manager, 'bypassV2'),
651
- cid: this.cid,
652
- configModule: this.configModule,
653
- payload: {
654
- data: {
655
- enabled: false,
656
- id: 0
657
- },
658
- method: 'setSwitch',
659
- source: 'APP'
660
- }
661
- },
662
- Helpers.reqHeaderBypass()
663
- );
664
-
665
- const success = this.checkResponse([response, status], 'turnOff');
666
- if (!success) {
667
- logger.error(`Failed to turn off device: ${this.deviceName}`);
668
- }
669
- return success;
670
- }
671
-
672
803
  /**
673
804
  * Set target humidity
674
805
  */
@@ -938,6 +1069,25 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
938
1069
  logger.debug(`Initialized VeSyncWarmHumidifier device: ${this.deviceName}`);
939
1070
  }
940
1071
 
1072
+ /**
1073
+ * Get device details
1074
+ */
1075
+ async getDetails(): Promise<Boolean> {
1076
+ const success = await super.getDetails();
1077
+ if (success && this.details) {
1078
+ // Add warm humidifier specific fields
1079
+ this.details = {
1080
+ ...this.details,
1081
+ warm_enabled: this.details.warm_level > 0,
1082
+ drying_mode_enabled: this.details.autoDryingSwitch === 1,
1083
+ drying_mode_state: this.details.dryingMode || '',
1084
+ drying_mode_level: this.details.dryingLevel || 0,
1085
+ drying_mode_seconds_remaining: this.details.dryingRemainTime || 0
1086
+ };
1087
+ }
1088
+ return success;
1089
+ }
1090
+
941
1091
  /**
942
1092
  * Change fan speed - Implemented to satisfy interface but redirects to setMistLevel
943
1093
  */
@@ -976,7 +1126,9 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
976
1126
  );
977
1127
 
978
1128
  const success = this.checkResponse([response, status], 'setMode');
979
- if (!success) {
1129
+ if (success) {
1130
+ this.details.mode = mode;
1131
+ } else {
980
1132
  logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
981
1133
  }
982
1134
  return success;
@@ -1018,7 +1170,10 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1018
1170
  );
1019
1171
 
1020
1172
  const success = this.checkResponse([response, status], 'setWarmLevel');
1021
- if (!success) {
1173
+ if (success) {
1174
+ this.details.warm_level = level;
1175
+ this.details.warm_enabled = level > 0;
1176
+ } else {
1022
1177
  logger.error(`Failed to set warm level to ${level} for device: ${this.deviceName}`);
1023
1178
  }
1024
1179
  return success;
@@ -1054,11 +1209,49 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1054
1209
  );
1055
1210
 
1056
1211
  const success = this.checkResponse([response, status], 'setDryingModeEnabled');
1057
- if (!success) {
1212
+ if (success) {
1213
+ this.details.drying_mode_enabled = enabled;
1214
+ this.details.autoDryingSwitch = enabled ? 1 : 0;
1215
+ } else {
1058
1216
  logger.error(`Failed to set drying mode to ${enabled} for device: ${this.deviceName}`);
1059
1217
  }
1060
1218
  return success;
1061
1219
  }
1220
+
1221
+ /**
1222
+ * Get warm mist enabled status
1223
+ */
1224
+ get warmMistEnabled(): boolean {
1225
+ return this.details.warm_enabled || false;
1226
+ }
1227
+
1228
+ /**
1229
+ * Get drying mode enabled status
1230
+ */
1231
+ get dryingModeEnabled(): boolean {
1232
+ return this.details.drying_mode_enabled || false;
1233
+ }
1234
+
1235
+ /**
1236
+ * Get drying mode state
1237
+ */
1238
+ get dryingModeState(): string {
1239
+ return this.details.drying_mode_state || '';
1240
+ }
1241
+
1242
+ /**
1243
+ * Get drying mode level
1244
+ */
1245
+ get dryingModeLevel(): number {
1246
+ return this.details.drying_mode_level || 0;
1247
+ }
1248
+
1249
+ /**
1250
+ * Get drying mode seconds remaining
1251
+ */
1252
+ get dryingModeSecondsRemaining(): number {
1253
+ return this.details.drying_mode_seconds_remaining || 0;
1254
+ }
1062
1255
  }
1063
1256
 
1064
1257
  // Export fan modules dictionary