tsvesync 1.0.70 → 1.0.72

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.
@@ -0,0 +1,276 @@
1
+ import { VeSyncHumid200300S } from './humid200300S';
2
+ import { VeSync } from '../vesync';
3
+ import { Helpers } from '../helpers';
4
+ import { logger } from '../logger';
5
+
6
+ /**
7
+ * VeSync Humid1000S Humidifier Class
8
+ * Implementation based on PyVeSync's VeSyncHumid1000S class
9
+ * For OASISMIST1000S models (LUH-M101S-WUS and LUH-M101S-WEUR)
10
+ */
11
+ export class VeSyncHumid1000S extends VeSyncHumid200300S {
12
+ protected readonly _apiModes = [
13
+ 'getHumidifierStatus',
14
+ 'setSwitch',
15
+ 'setVirtualLevel',
16
+ 'setTargetHumidity',
17
+ 'setHumidityMode',
18
+ 'setDisplay',
19
+ 'setNightLight',
20
+ 'setAutoStopSwitch',
21
+ ];
22
+
23
+ constructor(details: Record<string, any>, manager: VeSync) {
24
+ super(details, manager);
25
+
26
+ // Update config for Humid1000S
27
+ this.config = {
28
+ module: 'VeSyncHumid1000S',
29
+ features: ['display', 'humidity', 'mist', 'timer', 'auto_mode', 'night_light'],
30
+ levels: [1, 2, 3, 4, 5, 6, 7, 8, 9]
31
+ };
32
+
33
+ logger.debug(`Initialized VeSyncHumid1000S device: ${this.deviceName}`);
34
+ }
35
+
36
+ /**
37
+ * Build API dictionary for humidifier
38
+ */
39
+ protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
40
+ if (!this._apiModes.includes(method)) {
41
+ logger.debug(`Invalid mode - ${method}`);
42
+ throw new Error(`Invalid mode - ${method}`);
43
+ }
44
+
45
+ const head = Helpers.reqHeaderBypass();
46
+ const body = {
47
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
48
+ cid: this.cid,
49
+ configModule: this.configModule,
50
+ payload: {
51
+ method: method,
52
+ source: 'APP',
53
+ data: {}
54
+ }
55
+ };
56
+
57
+ return [head, body];
58
+ }
59
+
60
+ /**
61
+ * Get device details
62
+ */
63
+ async getDetails(): Promise<Boolean> {
64
+ logger.debug(`Getting details for device: ${this.deviceName}`);
65
+ const [head, body] = this.buildApiDict('getHumidifierStatus');
66
+
67
+ const [response, status] = await this.callApi(
68
+ '/cloud/v2/deviceManaged/bypassV2',
69
+ 'post',
70
+ body,
71
+ head
72
+ );
73
+
74
+ const success = this.checkResponse([response, status], 'getDetails');
75
+ if (success && response?.result?.result) {
76
+ const result = response.result.result;
77
+
78
+ // Update device status based on powerSwitch field
79
+ this.deviceStatus = result.powerSwitch === 1 ? 'on' : 'off';
80
+
81
+ // Build details object with Humid1000S specific fields
82
+ this.details = {
83
+ mode: result.workMode || '',
84
+ humidity: result.humidity || 0,
85
+ target_humidity: result.targetHumidity || 0,
86
+ mist_level: result.mistLevel || 0,
87
+ mist_virtual_level: result.virtualLevel || 0,
88
+ water_lacks: result.waterLacksState || false,
89
+ water_tank_lifted: result.waterTankLifted || false,
90
+ display: result.screenSwitch === 1,
91
+ night_light_status: result.nightLightStatus || 'off',
92
+ night_light_brightness: result.nightLightBrightness || 0,
93
+ connection_status: result.connectionStatus || null,
94
+ configuration: result.configuration || {}
95
+ };
96
+
97
+ logger.debug(`Successfully got details for device: ${this.deviceName}`);
98
+ }
99
+ return success;
100
+ }
101
+
102
+ /**
103
+ * Turn device on
104
+ */
105
+ async turnOn(): Promise<boolean> {
106
+ return this.toggleSwitch(true);
107
+ }
108
+
109
+ /**
110
+ * Turn device off
111
+ */
112
+ async turnOff(): Promise<boolean> {
113
+ return this.toggleSwitch(false);
114
+ }
115
+
116
+ /**
117
+ * Toggle device power
118
+ */
119
+ async toggleSwitch(enabled: boolean): Promise<boolean> {
120
+ logger.info(`Setting device power to ${enabled ? 'on' : 'off'}: ${this.deviceName}`);
121
+ const [head, body] = this.buildApiDict('setSwitch');
122
+
123
+ body.payload.data = {
124
+ powerSwitch: enabled ? 1 : 0,
125
+ switchIdx: 0
126
+ };
127
+
128
+ const [response, status] = await this.callApi(
129
+ '/cloud/v2/deviceManaged/bypassV2',
130
+ 'post',
131
+ body,
132
+ head
133
+ );
134
+
135
+ const success = this.checkResponse([response, status], 'toggleSwitch');
136
+ if (success) {
137
+ this.deviceStatus = enabled ? 'on' : 'off';
138
+ return true;
139
+ }
140
+ logger.error(`Failed to set device power to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * Set night light
146
+ */
147
+ async setNightLight(enabled: boolean, brightness?: number): Promise<boolean> {
148
+ if (!this.hasFeature('night_light')) {
149
+ logger.error(`Night light feature not supported for device: ${this.deviceName}`);
150
+ return false;
151
+ }
152
+
153
+ logger.debug(`Setting night light to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
154
+ const [head, body] = this.buildApiDict('setNightLight');
155
+
156
+ body.payload.data = {
157
+ nightLightSwitch: enabled ? 1 : 0
158
+ };
159
+
160
+ // Add brightness if provided
161
+ if (enabled && brightness !== undefined) {
162
+ if (brightness < 1 || brightness > 100) {
163
+ logger.error(`Invalid night light brightness: ${brightness}. Must be between 1 and 100.`);
164
+ return false;
165
+ }
166
+ body.payload.data.nightLightBrightness = brightness;
167
+ }
168
+
169
+ const [response, status] = await this.callApi(
170
+ '/cloud/v2/deviceManaged/bypassV2',
171
+ 'post',
172
+ body,
173
+ head
174
+ );
175
+
176
+ const success = this.checkResponse([response, status], 'setNightLight');
177
+ if (success) {
178
+ this.details.night_light_status = enabled ? 'on' : 'off';
179
+ if (enabled && brightness !== undefined) {
180
+ this.details.night_light_brightness = brightness;
181
+ }
182
+ return true;
183
+ }
184
+
185
+ // Check for API error code 11000000 (feature not supported)
186
+ if (response?.code === 11000000) {
187
+ logger.warn(`Night light control not supported via API for device: ${this.deviceName}`);
188
+ return false;
189
+ }
190
+
191
+ logger.error(`Failed to set night light for device: ${this.deviceName}`);
192
+ return false;
193
+ }
194
+
195
+ // Connection status is available in details.connection_status
196
+
197
+ /**
198
+ * Get night light status
199
+ * Returns the night light status (on/off)
200
+ */
201
+ get nightLightStatus(): string {
202
+ return this.details.night_light_status || 'off';
203
+ }
204
+
205
+ /**
206
+ * Get night light brightness
207
+ * Returns the night light brightness (0-100)
208
+ */
209
+ get nightLightBrightness(): number {
210
+ return this.details.night_light_brightness || 0;
211
+ }
212
+
213
+ /**
214
+ * Set automatic stop
215
+ * Enable or disable automatic stop when target humidity is reached
216
+ */
217
+ async setAutomaticStop(enabled: boolean): Promise<boolean> {
218
+ logger.debug(`Setting automatic stop to ${enabled ? 'enabled' : 'disabled'} for device: ${this.deviceName}`);
219
+ const [head, body] = this.buildApiDict('setAutoStopSwitch');
220
+
221
+ body.payload.data = {
222
+ autoStopSwitch: enabled ? 1 : 0
223
+ };
224
+
225
+ const [response, status] = await this.callApi(
226
+ '/cloud/v2/deviceManaged/bypassV2',
227
+ 'post',
228
+ body,
229
+ head
230
+ );
231
+
232
+ const success = this.checkResponse([response, status], 'setAutomaticStop');
233
+ if (success) {
234
+ if (!this.details.configuration) {
235
+ this.details.configuration = {};
236
+ }
237
+ this.details.configuration.automatic_stop = enabled;
238
+ return true;
239
+ }
240
+
241
+ logger.error(`Failed to set automatic stop for device: ${this.deviceName}`);
242
+ return false;
243
+ }
244
+
245
+ /**
246
+ * Turn automatic stop on
247
+ * Enable automatic stop when target humidity is reached
248
+ */
249
+ async automaticStopOn(): Promise<boolean> {
250
+ return this.setAutomaticStop(true);
251
+ }
252
+
253
+ /**
254
+ * Turn automatic stop off
255
+ * Disable automatic stop when target humidity is reached
256
+ */
257
+ async automaticStopOff(): Promise<boolean> {
258
+ return this.setAutomaticStop(false);
259
+ }
260
+
261
+ /**
262
+ * Get target humidity
263
+ * Returns the target humidity setting
264
+ */
265
+ get targetHumidity(): number {
266
+ return this.details.target_humidity || 0;
267
+ }
268
+
269
+ /**
270
+ * Get automatic stop setting
271
+ * Returns whether automatic stop is enabled
272
+ */
273
+ get automaticStop(): boolean {
274
+ return this.details.configuration?.automatic_stop || false;
275
+ }
276
+ }
@@ -0,0 +1,199 @@
1
+ import { VeSyncHumidifier } from './humidifier';
2
+ import { VeSync } from '../vesync';
3
+ import { Helpers } from '../helpers';
4
+ import { logger } from '../logger';
5
+
6
+ /**
7
+ * VeSync Humid200S Humidifier Class
8
+ * Implementation based on PyVeSync's VeSyncHumid200S class
9
+ * For Classic200S models
10
+ */
11
+ export class VeSyncHumid200S extends VeSyncHumidifier {
12
+ protected readonly modes = ['auto', 'manual', 'sleep'] as const;
13
+ protected readonly features = ['humidity', 'mist', 'display', 'timer', 'auto_mode'];
14
+ protected readonly mistLevels = [1, 2, 3];
15
+ protected readonly humidityRange = { min: 30, max: 80 };
16
+
17
+ constructor(details: Record<string, any>, manager: VeSync) {
18
+ super(details, manager);
19
+
20
+ // Update config for Humid200S
21
+ this.config = {
22
+ module: 'VeSyncHumid200S',
23
+ features: ['display', 'humidity', 'mist', 'timer', 'auto_mode'],
24
+ levels: [1, 2, 3]
25
+ };
26
+
27
+ logger.debug(`Initialized VeSyncHumid200S device: ${this.deviceName}`);
28
+ }
29
+
30
+ /**
31
+ * Get device details
32
+ */
33
+ async getDetails(): Promise<Boolean> {
34
+ logger.debug(`Getting details for device: ${this.deviceName}`);
35
+ const [response, status] = await this.callApi(
36
+ '/cloud/v2/deviceManaged/bypassV2',
37
+ 'post',
38
+ {
39
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
40
+ cid: this.cid,
41
+ configModule: this.configModule,
42
+ payload: {
43
+ data: {},
44
+ method: 'getHumidifierStatus',
45
+ source: 'APP'
46
+ }
47
+ },
48
+ Helpers.reqHeaderBypass()
49
+ );
50
+
51
+ const success = this.checkResponse([response, status], 'getDetails');
52
+ if (success && response?.result?.result) {
53
+ const result = response.result.result;
54
+
55
+ // Update device status based on enabled field
56
+ this.deviceStatus = result.enabled ? 'on' : 'off';
57
+
58
+ this.details = {
59
+ mode: result.mode || '',
60
+ humidity: result.humidity || 0,
61
+ target_humidity: result.configuration?.auto_target_humidity || result.target_humidity || 0,
62
+ mist_level: result.mist_level || 0,
63
+ water_lacks: result.water_lacks || false,
64
+ humidity_high: result.humidity_high || false,
65
+ water_tank_lifted: result.water_tank_lifted || false,
66
+ display: result.display || false,
67
+ automatic_stop: result.automatic_stop_reach_target || false,
68
+ automatic_stop_configured: result.configuration?.automatic_stop || false,
69
+ auto_target_humidity: result.configuration?.auto_target_humidity || 0,
70
+ configuration: result.configuration || {}
71
+ };
72
+
73
+ logger.debug(`Successfully got details for device: ${this.deviceName}`);
74
+ }
75
+ return success;
76
+ }
77
+
78
+ /**
79
+ * Set display
80
+ * Override to use setIndicatorLightSwitch method
81
+ */
82
+ async setDisplay(enabled: boolean): Promise<boolean> {
83
+ logger.info(`Setting display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
84
+ const [response, status] = await this.callApi(
85
+ '/cloud/v2/deviceManaged/bypassV2',
86
+ 'post',
87
+ {
88
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
89
+ cid: this.cid,
90
+ configModule: this.configModule,
91
+ payload: {
92
+ data: {
93
+ enabled: enabled
94
+ },
95
+ method: 'setIndicatorLightSwitch',
96
+ source: 'APP'
97
+ }
98
+ },
99
+ Helpers.reqHeaderBypass()
100
+ );
101
+
102
+ const success = this.checkResponse([response, status], 'setDisplay');
103
+ if (success) {
104
+ this.details.display = enabled;
105
+ // Get latest state and log it
106
+ await this.getDetails();
107
+ logger.debug(`Device state after display change: ${JSON.stringify(this.details)}`);
108
+ } else {
109
+ logger.error(`Failed to set display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
110
+ }
111
+ return success;
112
+ }
113
+
114
+ /**
115
+ * Set mist level
116
+ */
117
+ async setMistLevel(level: number): Promise<boolean> {
118
+ if (!this.mistLevels.includes(level)) {
119
+ const error = `Invalid mist level: ${level}. Must be one of: ${this.mistLevels.join(', ')}`;
120
+ logger.error(`${error} for device: ${this.deviceName}`);
121
+ throw new Error(error);
122
+ }
123
+
124
+ logger.info(`Setting mist level to ${level} for device: ${this.deviceName}`);
125
+ const [response, status] = await this.callApi(
126
+ '/cloud/v2/deviceManaged/bypassV2',
127
+ 'post',
128
+ {
129
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
130
+ cid: this.cid,
131
+ configModule: this.configModule,
132
+ payload: {
133
+ data: {
134
+ level: level
135
+ },
136
+ method: 'setMistLevel', // Use setMistLevel for Classic200S
137
+ source: 'APP'
138
+ }
139
+ },
140
+ Helpers.reqHeaderBypass()
141
+ );
142
+
143
+ const success = this.checkResponse([response, status], 'setMistLevel');
144
+ if (success) {
145
+ this.details.mist_level = level;
146
+ // Get latest state and log it
147
+ await this.getDetails();
148
+ logger.debug(`Device state after mist level change: ${JSON.stringify(this.details)}`);
149
+ } else {
150
+ logger.error(`Failed to set mist level to ${level} for device: ${this.deviceName}`);
151
+ }
152
+ return success;
153
+ }
154
+
155
+ /**
156
+ * Get current humidity
157
+ * Provides access to the current humidity reading
158
+ */
159
+ get currentHumidity(): number {
160
+ return this.details.humidity || 0;
161
+ }
162
+
163
+ /**
164
+ * Get target humidity
165
+ */
166
+ get targetHumidity(): number {
167
+ return this.details.target_humidity || 0;
168
+ }
169
+
170
+ /**
171
+ * Get water lacks status
172
+ */
173
+ get waterLacks(): boolean {
174
+ return this.details.water_lacks || false;
175
+ }
176
+
177
+ /**
178
+ * Get water tank lifted status
179
+ */
180
+ get waterTankLifted(): boolean {
181
+ return this.details.water_tank_lifted || false;
182
+ }
183
+
184
+ /**
185
+ * Return JSON details for humidifier
186
+ * Override to include current humidity and target humidity
187
+ */
188
+ displayJSON(): string {
189
+ const baseInfo = JSON.parse(super.displayJSON());
190
+
191
+ // Add current humidity from details
192
+ baseInfo['Humidity'] = this.details.humidity?.toString() || '0';
193
+
194
+ // Add target humidity from configuration
195
+ baseInfo['Target Humidity'] = this.details.target_humidity?.toString() || '0';
196
+
197
+ return JSON.stringify(baseInfo, null, 4);
198
+ }
199
+ }
@@ -475,4 +475,36 @@ export class VeSyncHumidifier extends VeSyncFan {
475
475
  get currentHumidity(): number {
476
476
  return this.details.humidity || 0;
477
477
  }
478
+
479
+ /**
480
+ * Check if water tank is empty
481
+ * Returns true if water tank is empty and needs to be refilled
482
+ */
483
+ get waterLacks(): boolean {
484
+ return this.details.water_lacks || false;
485
+ }
486
+
487
+ /**
488
+ * Check if water tank is lifted
489
+ * Returns true if water tank is lifted/removed from the device
490
+ */
491
+ get waterTankLifted(): boolean {
492
+ return this.details.water_tank_lifted || false;
493
+ }
494
+
495
+ /**
496
+ * Check if humidity is higher than target
497
+ * Returns true if current humidity is higher than target humidity
498
+ */
499
+ get humidityHigh(): boolean {
500
+ return this.details.humidity_high || false;
501
+ }
502
+
503
+ /**
504
+ * Check if automatic stop is active
505
+ * Returns true if automatic stop is currently active
506
+ */
507
+ get automaticStop(): boolean {
508
+ return this.details.automatic_stop || false;
509
+ }
478
510
  }
@@ -4,6 +4,9 @@ import { VeSyncTowerFan } from './towerFan';
4
4
  import { VeSyncHumidifier } from './humidifier';
5
5
  import { VeSyncWarmHumidifier } from './warmHumidifier';
6
6
  import { VeSyncHumid200300S } from './humid200300S';
7
+ import { VeSyncSuperior6000S } from './superior6000S';
8
+ import { VeSyncHumid1000S } from './humid1000S';
9
+ import { VeSyncHumid200S } from './humid200S';
7
10
  import { VeSyncFan } from '../vesyncFan';
8
11
  import { VeSync } from '../vesync';
9
12
 
@@ -14,6 +17,9 @@ export { VeSyncTowerFan } from './towerFan';
14
17
  export { VeSyncHumidifier } from './humidifier';
15
18
  export { VeSyncWarmHumidifier } from './warmHumidifier';
16
19
  export { VeSyncHumid200300S } from './humid200300S';
20
+ export { VeSyncSuperior6000S } from './superior6000S';
21
+ export { VeSyncHumid1000S } from './humid1000S';
22
+ export { VeSyncHumid200S } from './humid200S';
17
23
 
18
24
  // Export fan modules dictionary
19
25
  export const fanModules: Record<string, new (details: Record<string, any>, manager: VeSync) => VeSyncFan> = {
@@ -66,7 +72,7 @@ export const fanModules: Record<string, new (details: Record<string, any>, manag
66
72
 
67
73
  // Classic Series
68
74
  'Classic300S': VeSyncHumidifier,
69
- 'Classic200S': VeSyncHumidifier,
75
+ 'Classic200S': VeSyncHumid200S,
70
76
 
71
77
  // Dual Series
72
78
  'Dual200S': VeSyncHumidifier,
@@ -88,12 +94,12 @@ export const fanModules: Record<string, new (details: Record<string, any>, manag
88
94
  'LUH-O451S-WUSR': VeSyncHumid200300S,
89
95
  'LUH-O601S-WUS': VeSyncHumid200300S,
90
96
  'LUH-O601S-KUS': VeSyncHumid200300S,
91
- 'LUH-M101S-WUS': VeSyncHumid200300S,
92
- 'LUH-M101S-WEUR': VeSyncHumid200300S,
97
+ 'LUH-M101S-WUS': VeSyncHumid1000S,
98
+ 'LUH-M101S-WEUR': VeSyncHumid1000S,
93
99
 
94
- // LEH Series
95
- 'LEH-S601S-WUS': VeSyncHumid200300S,
96
- 'LEH-S601S-WUSR': VeSyncHumid200300S,
100
+ // LEH Series - Superior 6000S
101
+ 'LEH-S601S-WUS': VeSyncSuperior6000S,
102
+ 'LEH-S601S-WUSR': VeSyncSuperior6000S,
97
103
 
98
104
  // LV Series
99
105
  'LV-PUR131S': VeSyncAirBypass,