tsvesync 1.0.35 → 1.0.37

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.
package/README.md CHANGED
@@ -121,6 +121,10 @@ A TypeScript library for interacting with VeSync smart home devices. This librar
121
121
  - Power monitoring
122
122
  - Energy usage tracking (daily/weekly/monthly/yearly)
123
123
  - Voltage monitoring
124
+ - 10A Outlet (ESW10-USA)
125
+ - Power monitoring
126
+ - Energy usage tracking (daily/weekly/monthly/yearly)
127
+ - Voltage monitoring
124
128
  - 7A Outlet (wifi-switch-1.3)
125
129
  - Energy usage tracking (daily/weekly/monthly/yearly)
126
130
 
@@ -0,0 +1,156 @@
1
+ get_monthly_energy:
2
+ headers:
3
+ accept-language: en
4
+ accountId: sample_id
5
+ appVersion: 2.8.6
6
+ content-type: application/json
7
+ tk: sample_tk
8
+ tz: America/New_York
9
+ json_object:
10
+ acceptLanguage: en
11
+ accountID: sample_id
12
+ appVersion: 2.8.6
13
+ method: energymonth
14
+ mobileId: '1234567890123456'
15
+ phoneBrand: SM N9005
16
+ phoneOS: Android
17
+ timeZone: America/New_York
18
+ token: sample_tk
19
+ traceId: TRACE_ID
20
+ uuid: ESW10-USA-UUID
21
+ method: post
22
+ url: /15a/v1/device/energymonth
23
+ get_weekly_energy:
24
+ headers:
25
+ accept-language: en
26
+ accountId: sample_id
27
+ appVersion: 2.8.6
28
+ content-type: application/json
29
+ tk: sample_tk
30
+ tz: America/New_York
31
+ json_object:
32
+ acceptLanguage: en
33
+ accountID: sample_id
34
+ appVersion: 2.8.6
35
+ method: energyweek
36
+ mobileId: '1234567890123456'
37
+ phoneBrand: SM N9005
38
+ phoneOS: Android
39
+ timeZone: America/New_York
40
+ token: sample_tk
41
+ traceId: TRACE_ID
42
+ uuid: ESW10-USA-UUID
43
+ method: post
44
+ url: /15a/v1/device/energyweek
45
+ get_yearly_energy:
46
+ headers:
47
+ accept-language: en
48
+ accountId: sample_id
49
+ appVersion: 2.8.6
50
+ content-type: application/json
51
+ tk: sample_tk
52
+ tz: America/New_York
53
+ json_object:
54
+ acceptLanguage: en
55
+ accountID: sample_id
56
+ appVersion: 2.8.6
57
+ method: energyyear
58
+ mobileId: '1234567890123456'
59
+ phoneBrand: SM N9005
60
+ phoneOS: Android
61
+ timeZone: America/New_York
62
+ token: sample_tk
63
+ traceId: TRACE_ID
64
+ uuid: ESW10-USA-UUID
65
+ method: post
66
+ url: /15a/v1/device/energyyear
67
+ turn_off:
68
+ headers:
69
+ accept-language: en
70
+ accountId: sample_id
71
+ appVersion: 2.8.6
72
+ content-type: application/json
73
+ tk: sample_tk
74
+ tz: America/New_York
75
+ json_object:
76
+ acceptLanguage: en
77
+ accountID: sample_id
78
+ status: 'off'
79
+ timeZone: America/New_York
80
+ token: sample_tk
81
+ uuid: ESW10-USA-UUID
82
+ method: put
83
+ url: /15a/v1/device/devicestatus
84
+ turn_off_nightlight:
85
+ headers:
86
+ accept-language: en
87
+ accountId: sample_id
88
+ appVersion: 2.8.6
89
+ content-type: application/json
90
+ tk: sample_tk
91
+ tz: America/New_York
92
+ json_object:
93
+ acceptLanguage: en
94
+ accountID: sample_id
95
+ mode: manual
96
+ timeZone: America/New_York
97
+ token: sample_tk
98
+ uuid: ESW10-USA-UUID
99
+ method: put
100
+ url: /15a/v1/device/nightlightstatus
101
+ turn_on:
102
+ headers:
103
+ accept-language: en
104
+ accountId: sample_id
105
+ appVersion: 2.8.6
106
+ content-type: application/json
107
+ tk: sample_tk
108
+ tz: America/New_York
109
+ json_object:
110
+ acceptLanguage: en
111
+ accountID: sample_id
112
+ status: 'on'
113
+ timeZone: America/New_York
114
+ token: sample_tk
115
+ uuid: ESW10-USA-UUID
116
+ method: put
117
+ url: /15a/v1/device/devicestatus
118
+ turn_on_nightlight:
119
+ headers:
120
+ accept-language: en
121
+ accountId: sample_id
122
+ appVersion: 2.8.6
123
+ content-type: application/json
124
+ tk: sample_tk
125
+ tz: America/New_York
126
+ json_object:
127
+ acceptLanguage: en
128
+ accountID: sample_id
129
+ mode: auto
130
+ timeZone: America/New_York
131
+ token: sample_tk
132
+ uuid: ESW10-USA-UUID
133
+ method: put
134
+ url: /15a/v1/device/nightlightstatus
135
+ update:
136
+ headers:
137
+ accept-language: en
138
+ accountId: sample_id
139
+ appVersion: 2.8.6
140
+ content-type: application/json
141
+ tk: sample_tk
142
+ tz: America/New_York
143
+ json_object:
144
+ acceptLanguage: en
145
+ accountID: sample_id
146
+ appVersion: 2.8.6
147
+ method: devicedetail
148
+ mobileId: '1234567890123456'
149
+ phoneBrand: SM N9005
150
+ phoneOS: Android
151
+ timeZone: America/New_York
152
+ token: sample_tk
153
+ traceId: TRACE_ID
154
+ uuid: ESW10-USA-UUID
155
+ method: post
156
+ url: /15a/v1/device/devicedetail
@@ -52,6 +52,7 @@ function objectFactory(details, manager) {
52
52
  'wifi-switch': 'outlets',
53
53
  'ESW03': 'outlets',
54
54
  'ESW01': 'outlets',
55
+ 'ESW10': 'outlets',
55
56
  'ESW15': 'outlets',
56
57
  'ESO': 'outlets',
57
58
  // Switches
@@ -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
@@ -21,6 +21,10 @@ exports.outletConfig = {
21
21
  module: 'VeSyncOutlet10A',
22
22
  features: ['energy']
23
23
  },
24
+ 'ESW10-USA': {
25
+ module: 'VeSyncOutlet10A',
26
+ features: ['energy']
27
+ },
24
28
  'ESW15-USA': {
25
29
  module: 'VeSyncOutlet15A',
26
30
  features: ['energy', 'nightlight']
@@ -60,7 +64,7 @@ class VeSyncOutlet extends vesyncBaseDevice_1.VeSyncBaseDevice {
60
64
  else if (this.deviceType.startsWith('ESW15')) {
61
65
  url = '/15a/v1/device/devicedetail';
62
66
  }
63
- else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01')) {
67
+ else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) {
64
68
  url = '/10a/v1/device/devicedetail';
65
69
  }
66
70
  else {
@@ -147,7 +151,7 @@ class VeSyncOutlet extends vesyncBaseDevice_1.VeSyncBaseDevice {
147
151
  else if (this.deviceType.startsWith('ESW15')) {
148
152
  url = '/15a/v1/device/energy';
149
153
  }
150
- else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01')) {
154
+ else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) {
151
155
  url = '/10a/v1/device/energy';
152
156
  }
153
157
  else {
@@ -233,7 +237,7 @@ class VeSyncOutlet extends vesyncBaseDevice_1.VeSyncBaseDevice {
233
237
  else if (this.deviceType.startsWith('ESW15') || this.deviceType.startsWith('ESO15')) {
234
238
  return '15a/v1/device';
235
239
  }
236
- else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01')) {
240
+ else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) {
237
241
  return '10a/v1/device';
238
242
  }
239
243
  return 'v1/device';
@@ -242,6 +242,7 @@ exports.outletModules = {
242
242
  'wifi-switch-1.3': VeSyncOutlet7A,
243
243
  'ESW03-USA': VeSyncOutlet10A,
244
244
  'ESW01-EU': VeSyncOutlet10A,
245
+ 'ESW10-USA': VeSyncOutlet10A,
245
246
  'ESW15-USA': VeSyncOutlet15A,
246
247
  'ESO15-TB': VeSyncOutdoorPlug
247
248
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
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",
package/src/lib/vesync.ts CHANGED
@@ -67,6 +67,7 @@ function objectFactory(details: Record<string, any>, manager: VeSync): [string,
67
67
  'wifi-switch': 'outlets',
68
68
  'ESW03': 'outlets',
69
69
  'ESW01': 'outlets',
70
+ 'ESW10': 'outlets',
70
71
  'ESW15': 'outlets',
71
72
  'ESO': 'outlets',
72
73
  // Switches
@@ -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
@@ -28,6 +28,10 @@ export const outletConfig: OutletConfig = {
28
28
  module: 'VeSyncOutlet10A',
29
29
  features: ['energy']
30
30
  },
31
+ 'ESW10-USA': {
32
+ module: 'VeSyncOutlet10A',
33
+ features: ['energy']
34
+ },
31
35
  'ESW15-USA': {
32
36
  module: 'VeSyncOutlet15A',
33
37
  features: ['energy', 'nightlight']
@@ -71,7 +75,7 @@ export abstract class VeSyncOutlet extends VeSyncBaseDevice {
71
75
  url = '/outdoorsocket15a/v1/device/devicedetail';
72
76
  } else if (this.deviceType.startsWith('ESW15')) {
73
77
  url = '/15a/v1/device/devicedetail';
74
- } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01')) {
78
+ } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) {
75
79
  url = '/10a/v1/device/devicedetail';
76
80
  } else {
77
81
  url = '/v1/device/devicedetail';
@@ -168,7 +172,7 @@ export abstract class VeSyncOutlet extends VeSyncBaseDevice {
168
172
  url = '/outdoorsocket15a/v1/device/energy';
169
173
  } else if (this.deviceType.startsWith('ESW15')) {
170
174
  url = '/15a/v1/device/energy';
171
- } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01')) {
175
+ } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) {
172
176
  url = '/10a/v1/device/energy';
173
177
  } else {
174
178
  url = '/v1/device/energy';
@@ -261,7 +265,7 @@ export abstract class VeSyncOutlet extends VeSyncBaseDevice {
261
265
  return `v1/device/${this.deviceType}-${this.cid}`;
262
266
  } else if (this.deviceType.startsWith('ESW15') || this.deviceType.startsWith('ESO15')) {
263
267
  return '15a/v1/device';
264
- } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01')) {
268
+ } else if (this.deviceType.startsWith('ESW03') || this.deviceType.startsWith('ESW01') || this.deviceType.startsWith('ESW10')) {
265
269
  return '10a/v1/device';
266
270
  }
267
271
  return 'v1/device';
@@ -325,6 +325,7 @@ export const outletModules: Record<string, new (details: Record<string, any>, ma
325
325
  'wifi-switch-1.3': VeSyncOutlet7A,
326
326
  'ESW03-USA': VeSyncOutlet10A,
327
327
  'ESW01-EU': VeSyncOutlet10A,
328
+ 'ESW10-USA': VeSyncOutlet10A,
328
329
  'ESW15-USA': VeSyncOutlet15A,
329
330
  'ESO15-TB': VeSyncOutdoorPlug
330
331
  };