tsvesync 1.3.13 → 1.4.0

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.
@@ -57,6 +57,14 @@ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
57
57
  * Override setMode to send workMode payloads for bypassV2 purifiers.
58
58
  */
59
59
  setMode(mode: string): Promise<boolean>;
60
+ /**
61
+ * Convenience helper to enter turbo mode when supported.
62
+ */
63
+ turboMode(): Promise<boolean>;
64
+ /**
65
+ * Convenience helper to enter pet mode when supported.
66
+ */
67
+ petMode(): Promise<boolean>;
60
68
  /**
61
69
  * Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
62
70
  */
@@ -13,11 +13,11 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
13
13
  this._lightDetection = false;
14
14
  this._lightDetectionState = false;
15
15
  this.setSpeedLevel = null;
16
- this.autoPreferences = ['default', 'efficient', 'quiet'];
17
16
  this.enabled = false;
18
17
  this._mode = '';
19
18
  this._speed = 0;
20
19
  this._timer = null;
20
+ this.autoPreferences = [...this.getConfigAutoPreferences(['default', 'efficient', 'quiet'])];
21
21
  logger_1.logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
22
22
  }
23
23
  buildConfigDict(configDict) {
@@ -265,28 +265,63 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
265
265
  * Override setMode to send workMode payloads for bypassV2 purifiers.
266
266
  */
267
267
  async setMode(mode) {
268
- if (!this.modes.includes(mode)) {
268
+ var _a, _b;
269
+ const targetMode = mode.toLowerCase();
270
+ const supportedModes = this.modes.map(m => m.toLowerCase());
271
+ if (!supportedModes.includes(targetMode)) {
269
272
  const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
270
273
  logger_1.logger.error(`${error} for device: ${this.deviceName}`);
271
274
  throw new Error(error);
272
275
  }
273
276
  // Special handling for Core200S stays the same for all models
274
- if (this.deviceType.includes('Core200S') && mode === 'auto') {
277
+ if (this.deviceType.includes('Core200S') && targetMode === 'auto') {
275
278
  logger_1.logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
276
279
  return this.setMode('manual');
277
280
  }
281
+ // Manual mode should leverage changeFanSpeed to mirror pyvesync behaviour
282
+ if (targetMode === 'manual') {
283
+ const defaultLevel = this.config.levels && this.config.levels.length > 0
284
+ ? this.config.levels[0]
285
+ : 1;
286
+ const desiredLevel = (_b = (_a = this.details.manualSpeedLevel) !== null && _a !== void 0 ? _a : this.speed) !== null && _b !== void 0 ? _b : defaultLevel;
287
+ const success = await this.changeFanSpeed(desiredLevel);
288
+ if (success) {
289
+ this.details.mode = 'manual';
290
+ this.mode = 'manual';
291
+ }
292
+ return success;
293
+ }
278
294
  // BypassV2 purifiers need the workMode payload; other models use the base implementation
279
295
  if (this.requiresPowerSwitchPayload()) {
280
296
  logger_1.logger.debug(`Using bypassV2 mode payload for ${this.deviceName}`);
281
297
  const [head, body] = this.buildApiDict('setPurifierMode');
282
298
  body.payload.data = {
283
- workMode: mode
299
+ workMode: targetMode
284
300
  };
285
301
  const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
286
302
  const success = this.checkV2Response(response, status, 'setMode');
287
303
  if (success) {
288
- this.details.mode = mode;
289
- this._mode = mode;
304
+ this.details.mode = targetMode;
305
+ this.mode = targetMode;
306
+ // Update cached speed snapshot for special modes
307
+ if (targetMode === 'sleep') {
308
+ this.speed = 0;
309
+ this.details.speed = 0;
310
+ }
311
+ else if (targetMode === 'turbo') {
312
+ const maxSpeed = this.config.levels && this.config.levels.length > 0
313
+ ? Math.max(...this.config.levels)
314
+ : this.speed;
315
+ if (typeof maxSpeed === 'number' && maxSpeed > 0) {
316
+ this.speed = maxSpeed;
317
+ this.details.speed = maxSpeed;
318
+ }
319
+ }
320
+ else if (targetMode === 'auto') {
321
+ this.speed = 0;
322
+ this.details.speed = 0;
323
+ this.details.isAutoSentinel = true;
324
+ }
290
325
  // Force a details update to sync state
291
326
  setTimeout(() => {
292
327
  this.getDetails().catch(e => {
@@ -296,7 +331,7 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
296
331
  return true;
297
332
  }
298
333
  else {
299
- logger_1.logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
334
+ logger_1.logger.error(`Failed to set mode to ${targetMode} for device: ${this.deviceName}`);
300
335
  return false;
301
336
  }
302
337
  }
@@ -305,6 +340,26 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
305
340
  return super.setMode(mode);
306
341
  }
307
342
  }
343
+ /**
344
+ * Convenience helper to enter turbo mode when supported.
345
+ */
346
+ async turboMode() {
347
+ if (!this.modes.map(m => m.toLowerCase()).includes('turbo')) {
348
+ logger_1.logger.debug(`Turbo mode not supported for device: ${this.deviceName}`);
349
+ return false;
350
+ }
351
+ return this.setMode('turbo');
352
+ }
353
+ /**
354
+ * Convenience helper to enter pet mode when supported.
355
+ */
356
+ async petMode() {
357
+ if (!this.modes.map(m => m.toLowerCase()).includes('pet')) {
358
+ logger_1.logger.debug(`Pet mode not supported for device: ${this.deviceName}`);
359
+ return false;
360
+ }
361
+ return this.setMode('pet');
362
+ }
308
363
  /**
309
364
  * Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
310
365
  */
@@ -331,6 +386,10 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
331
386
  if (success) {
332
387
  this.speed = speed;
333
388
  this.details.manualSpeedLevel = speed;
389
+ this.details.speed = speed;
390
+ this.details.mode = 'manual';
391
+ this.mode = 'manual';
392
+ this.details.isAutoSentinel = false;
334
393
  // Force a details update to sync state
335
394
  setTimeout(() => {
336
395
  this.getDetails().catch(e => {
@@ -357,6 +416,10 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
357
416
  if (success) {
358
417
  this.speed = speed;
359
418
  this.details.manualSpeedLevel = speed;
419
+ this.details.speed = speed;
420
+ this.details.mode = 'manual';
421
+ this.mode = 'manual';
422
+ this.details.isAutoSentinel = false;
360
423
  return true;
361
424
  }
362
425
  else {
@@ -370,7 +433,7 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
370
433
  */
371
434
  async setAutoPreference(preference = 'default', roomSize = 600) {
372
435
  if (!this.autoPreferences.includes(preference)) {
373
- logger_1.logger.debug(`Invalid preference: ${preference} - valid preferences are default, efficient, quiet`);
436
+ logger_1.logger.debug(`Invalid preference: ${preference} - valid preferences are ${this.autoPreferences.join(', ')}`);
374
437
  return false;
375
438
  }
376
439
  const [head, body] = this.buildApiDict('setAutoPreference');
@@ -4,7 +4,7 @@ import { VeSync } from '../vesync';
4
4
  * VeSync Air Purifier with Bypass
5
5
  */
6
6
  export declare class VeSyncAirBypass extends VeSyncFan {
7
- protected readonly modes: readonly ["auto", "manual", "sleep"];
7
+ protected get modes(): ReadonlyArray<string>;
8
8
  protected readonly displayModes: readonly ["on", "off"];
9
9
  protected readonly childLockModes: readonly ["on", "off"];
10
10
  constructor(details: Record<string, any>, manager: VeSync);
@@ -8,9 +8,11 @@ const logger_1 = require("../logger");
8
8
  * VeSync Air Purifier with Bypass
9
9
  */
10
10
  class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
11
+ get modes() {
12
+ return this.getConfigModes(['auto', 'manual', 'sleep']);
13
+ }
11
14
  constructor(details, manager) {
12
15
  super(details, manager);
13
- this.modes = ['auto', 'manual', 'sleep'];
14
16
  this.displayModes = ['on', 'off'];
15
17
  this.childLockModes = ['on', 'off'];
16
18
  logger_1.logger.debug(`Initialized VeSyncAirBypass device: ${this.deviceName}`);
@@ -183,14 +185,17 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
183
185
  * Set device mode
184
186
  */
185
187
  async setMode(mode) {
186
- if (!this.modes.includes(mode)) {
188
+ const requested = mode.toLowerCase();
189
+ const supported = this.modes.map(m => m.toLowerCase());
190
+ const allowsCore200AutoFallback = this.deviceType.includes('Core200S') && requested === 'auto';
191
+ if (!supported.includes(requested) && !allowsCore200AutoFallback) {
187
192
  const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
188
193
  logger_1.logger.error(`${error} for device: ${this.deviceName}`);
189
194
  throw new Error(error);
190
195
  }
191
- logger_1.logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
196
+ logger_1.logger.debug(`Setting mode to ${requested} for device: ${this.deviceName}`);
192
197
  // Special handling for Core200S auto mode
193
- if (this.deviceType.includes('Core200S') && mode === 'auto') {
198
+ if (allowsCore200AutoFallback) {
194
199
  // Core200S doesn't support auto mode, so we'll set it to manual mode
195
200
  logger_1.logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
196
201
  return this.setMode('manual');
@@ -202,7 +207,7 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
202
207
  configModule: this.configModule,
203
208
  payload: {
204
209
  data: {
205
- mode
210
+ mode: requested
206
211
  },
207
212
  method: 'setPurifierMode',
208
213
  source: 'APP'
@@ -210,11 +215,11 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
210
215
  }, helpers_1.Helpers.reqHeaderBypass());
211
216
  const success = this.checkResponse([response, status], 'setMode');
212
217
  if (success) {
213
- this.details.mode = mode;
218
+ this.details.mode = requested;
214
219
  return true;
215
220
  }
216
221
  else {
217
- logger_1.logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
222
+ logger_1.logger.error(`Failed to set mode to ${requested} for device: ${this.deviceName}`);
218
223
  return false;
219
224
  }
220
225
  }
@@ -3,12 +3,15 @@
3
3
  */
4
4
  import { VeSyncBaseDevice } from './vesyncBaseDevice';
5
5
  import { VeSync } from './vesync';
6
+ interface FanConfigEntry {
7
+ module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
8
+ features: string[];
9
+ levels?: number[];
10
+ modes?: ReadonlyArray<string>;
11
+ autoPreferences?: ReadonlyArray<string>;
12
+ }
6
13
  interface FanConfig {
7
- [key: string]: {
8
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
9
- features: string[];
10
- levels?: number[];
11
- };
14
+ [key: string]: FanConfigEntry;
12
15
  }
13
16
  export declare const fanConfig: FanConfig;
14
17
  /**
@@ -42,16 +45,28 @@ export declare abstract class VeSyncFan extends VeSyncBaseDevice {
42
45
  duration: number;
43
46
  action: string;
44
47
  } | null;
45
- protected config: {
46
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
47
- features: string[];
48
- levels?: number[];
49
- };
48
+ protected config: FanConfigEntry;
50
49
  constructor(details: Record<string, any>, manager: VeSync);
51
50
  /**
52
51
  * Check if feature is supported
53
52
  */
54
53
  hasFeature(feature: string): boolean;
54
+ /**
55
+ * Get configured modes for the device, falling back to provided defaults.
56
+ */
57
+ protected getConfigModes(defaultModes?: ReadonlyArray<string>): ReadonlyArray<string>;
58
+ /**
59
+ * Get configured auto preferences for the device, falling back to provided defaults.
60
+ */
61
+ protected getConfigAutoPreferences(defaultPreferences?: ReadonlyArray<string>): ReadonlyArray<string>;
62
+ /**
63
+ * Public accessor for supported modes for consumers that need to inspect capability metadata.
64
+ */
65
+ getSupportedModes(defaultModes?: ReadonlyArray<string>): ReadonlyArray<string>;
66
+ /**
67
+ * Public accessor for supported auto preference profiles.
68
+ */
69
+ getSupportedAutoPreferences(defaultPreferences?: ReadonlyArray<string>): ReadonlyArray<string>;
55
70
  /**
56
71
  * Check if feature is supported in current mode
57
72
  */
@@ -13,158 +13,217 @@ exports.fanConfig = {
13
13
  'Core200S': {
14
14
  module: 'VeSyncAirBypass',
15
15
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
16
- levels: [1, 2, 3, 4] // 4 levels including sleep mode, matching reference plugin
16
+ levels: [1, 2, 3, 4], // 4 levels including sleep mode, matching reference plugin
17
+ modes: ['sleep', 'manual']
17
18
  },
18
19
  'Core300S': {
19
20
  module: 'VeSyncAirBypass',
20
21
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
21
- levels: [1, 2, 3, 4]
22
+ levels: [1, 2, 3, 4],
23
+ modes: ['sleep', 'manual', 'auto'],
24
+ autoPreferences: ['default', 'efficient', 'quiet']
22
25
  },
23
26
  'Core400S': {
24
27
  module: 'VeSyncAirBypass',
25
28
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
26
- levels: [1, 2, 3, 4]
29
+ levels: [1, 2, 3, 4],
30
+ modes: ['sleep', 'manual', 'auto'],
31
+ autoPreferences: ['default', 'efficient', 'quiet']
27
32
  },
28
33
  'Core600S': {
29
34
  module: 'VeSyncAirBypass',
30
35
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
31
- levels: [1, 2, 3, 4]
36
+ levels: [1, 2, 3, 4],
37
+ modes: ['sleep', 'manual', 'auto'],
38
+ autoPreferences: ['default', 'efficient', 'quiet']
32
39
  },
33
40
  // LAP Series
34
41
  'LAP-C201S-AUSR': {
35
42
  module: 'VeSyncAirBypass',
36
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
37
- levels: [1, 2, 3]
43
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
44
+ levels: [1, 2, 3],
45
+ modes: ['sleep', 'manual']
38
46
  },
39
47
  'LAP-C202S-WUSR': {
40
48
  module: 'VeSyncAirBypass',
41
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
42
- levels: [1, 2, 3]
49
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
50
+ levels: [1, 2, 3],
51
+ modes: ['sleep', 'manual']
43
52
  },
44
53
  'LAP-C301S-WJP': {
45
54
  module: 'VeSyncAirBypass',
46
55
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
47
- levels: [1, 2, 3, 4]
56
+ levels: [1, 2, 3, 4],
57
+ modes: ['sleep', 'manual', 'auto'],
58
+ autoPreferences: ['default', 'efficient', 'quiet']
48
59
  },
49
60
  'LAP-C302S-WUSB': {
50
61
  module: 'VeSyncAirBypass',
51
62
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
52
- levels: [1, 2, 3, 4]
63
+ levels: [1, 2, 3, 4],
64
+ modes: ['sleep', 'manual', 'auto'],
65
+ autoPreferences: ['default', 'efficient', 'quiet']
53
66
  },
54
67
  'LAP-C301S-WAAA': {
55
68
  module: 'VeSyncAirBypass',
56
69
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
57
- levels: [1, 2, 3, 4]
70
+ levels: [1, 2, 3, 4],
71
+ modes: ['sleep', 'manual', 'auto'],
72
+ autoPreferences: ['default', 'efficient', 'quiet']
58
73
  },
59
74
  'LAP-C401S-WJP': {
60
75
  module: 'VeSyncAirBypass',
61
76
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
62
- levels: [1, 2, 3, 4]
77
+ levels: [1, 2, 3, 4],
78
+ modes: ['sleep', 'manual', 'auto'],
79
+ autoPreferences: ['default', 'efficient', 'quiet']
63
80
  },
64
81
  'LAP-C401S-WUSR': {
65
82
  module: 'VeSyncAirBypass',
66
83
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
67
- levels: [1, 2, 3, 4]
84
+ levels: [1, 2, 3, 4],
85
+ modes: ['sleep', 'manual', 'auto'],
86
+ autoPreferences: ['default', 'efficient', 'quiet']
68
87
  },
69
88
  'LAP-C401S-WAAA': {
70
89
  module: 'VeSyncAirBypass',
71
90
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
72
- levels: [1, 2, 3, 4]
91
+ levels: [1, 2, 3, 4],
92
+ modes: ['sleep', 'manual', 'auto'],
93
+ autoPreferences: ['default', 'efficient', 'quiet']
73
94
  },
74
95
  'LAP-C601S-WUS': {
75
96
  module: 'VeSyncAirBypass',
76
97
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
77
- levels: [1, 2, 3, 4]
98
+ levels: [1, 2, 3, 4],
99
+ modes: ['sleep', 'manual', 'auto'],
100
+ autoPreferences: ['default', 'efficient', 'quiet']
78
101
  },
79
102
  'LAP-C601S-WUSR': {
80
103
  module: 'VeSyncAirBypass',
81
104
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
82
- levels: [1, 2, 3, 4]
105
+ levels: [1, 2, 3, 4],
106
+ modes: ['sleep', 'manual', 'auto'],
107
+ autoPreferences: ['default', 'efficient', 'quiet']
83
108
  },
84
109
  'LAP-C601S-WEU': {
85
110
  module: 'VeSyncAirBypass',
86
111
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
87
- levels: [1, 2, 3, 4]
112
+ levels: [1, 2, 3, 4],
113
+ modes: ['sleep', 'manual', 'auto'],
114
+ autoPreferences: ['default', 'efficient', 'quiet']
88
115
  },
89
116
  'LAP-V102S-AASR': {
90
117
  module: 'VeSyncAirBaseV2',
91
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
92
- levels: [1, 2, 3, 4]
118
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
119
+ levels: [1, 2, 3, 4],
120
+ modes: ['sleep', 'manual', 'auto', 'pet'],
121
+ autoPreferences: ['default', 'efficient', 'quiet']
93
122
  },
94
123
  'LAP-V102S-WUS': {
95
124
  module: 'VeSyncAirBaseV2',
96
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
97
- levels: [1, 2, 3, 4]
125
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
126
+ levels: [1, 2, 3, 4],
127
+ modes: ['sleep', 'manual', 'auto', 'pet'],
128
+ autoPreferences: ['default', 'efficient', 'quiet']
98
129
  },
99
130
  'LAP-V102S-WEU': {
100
131
  module: 'VeSyncAirBaseV2',
101
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
102
- levels: [1, 2, 3, 4]
132
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
133
+ levels: [1, 2, 3, 4],
134
+ modes: ['sleep', 'manual', 'auto', 'pet'],
135
+ autoPreferences: ['default', 'efficient', 'quiet']
103
136
  },
104
137
  'LAP-V102S-AUSR': {
105
138
  module: 'VeSyncAirBaseV2',
106
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
107
- levels: [1, 2, 3, 4]
139
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
140
+ levels: [1, 2, 3, 4],
141
+ modes: ['sleep', 'manual', 'auto', 'pet'],
142
+ autoPreferences: ['default', 'efficient', 'quiet']
108
143
  },
109
144
  'LAP-V102S-WJP': {
110
145
  module: 'VeSyncAirBaseV2',
111
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
112
- levels: [1, 2, 3, 4]
146
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
147
+ levels: [1, 2, 3, 4],
148
+ modes: ['sleep', 'manual', 'auto', 'pet'],
149
+ autoPreferences: ['default', 'efficient', 'quiet']
113
150
  },
114
151
  'LAP-V201S-AASR': {
115
152
  module: 'VeSyncAirBaseV2',
116
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
117
- levels: [1, 2, 3, 4]
153
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
154
+ levels: [1, 2, 3, 4],
155
+ modes: ['sleep', 'manual', 'auto', 'pet'],
156
+ autoPreferences: ['default', 'efficient', 'quiet']
118
157
  },
119
158
  'LAP-V201S-WJP': {
120
159
  module: 'VeSyncAirBaseV2',
121
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
122
- levels: [1, 2, 3, 4]
160
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
161
+ levels: [1, 2, 3, 4],
162
+ modes: ['sleep', 'manual', 'auto', 'pet'],
163
+ autoPreferences: ['default', 'efficient', 'quiet']
123
164
  },
124
165
  'LAP-V201S-WEU': {
125
166
  module: 'VeSyncAirBaseV2',
126
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
127
- levels: [1, 2, 3, 4]
167
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
168
+ levels: [1, 2, 3, 4],
169
+ modes: ['sleep', 'manual', 'auto', 'pet'],
170
+ autoPreferences: ['default', 'efficient', 'quiet']
128
171
  },
129
172
  'LAP-V201S-WUS': {
130
173
  module: 'VeSyncAirBaseV2',
131
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
132
- levels: [1, 2, 3, 4]
174
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
175
+ levels: [1, 2, 3, 4],
176
+ modes: ['sleep', 'manual', 'auto', 'pet'],
177
+ autoPreferences: ['default', 'efficient', 'quiet']
133
178
  },
134
179
  'LAP-V201-AUSR': {
135
180
  module: 'VeSyncAirBaseV2',
136
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
137
- levels: [1, 2, 3, 4]
181
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
182
+ levels: [1, 2, 3, 4],
183
+ modes: ['sleep', 'manual', 'auto', 'pet'],
184
+ autoPreferences: ['default', 'efficient', 'quiet']
138
185
  },
139
186
  'LAP-V201S-AUSR': {
140
187
  module: 'VeSyncAirBaseV2',
141
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
142
- levels: [1, 2, 3, 4]
188
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
189
+ levels: [1, 2, 3, 4],
190
+ modes: ['sleep', 'manual', 'auto', 'pet'],
191
+ autoPreferences: ['default', 'efficient', 'quiet']
143
192
  },
144
193
  'LAP-V201S-AEUR': {
145
194
  module: 'VeSyncAirBaseV2',
146
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
147
- levels: [1, 2, 3, 4]
195
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
196
+ levels: [1, 2, 3, 4],
197
+ modes: ['sleep', 'manual', 'auto', 'pet'],
198
+ autoPreferences: ['default', 'efficient', 'quiet']
148
199
  },
149
200
  'LAP-EL551S-AUS': {
150
201
  module: 'VeSyncAirBaseV2',
151
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
152
- levels: [1, 2, 3]
202
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
203
+ levels: [1, 2, 3],
204
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
205
+ autoPreferences: ['default', 'efficient', 'quiet']
153
206
  },
154
207
  'LAP-EL551S-AEUR': {
155
208
  module: 'VeSyncAirBaseV2',
156
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
157
- levels: [1, 2, 3]
209
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
210
+ levels: [1, 2, 3],
211
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
212
+ autoPreferences: ['default', 'efficient', 'quiet']
158
213
  },
159
214
  'LAP-EL551S-WEU': {
160
215
  module: 'VeSyncAirBaseV2',
161
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
162
- levels: [1, 2, 3]
216
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
217
+ levels: [1, 2, 3],
218
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
219
+ autoPreferences: ['default', 'efficient', 'quiet']
163
220
  },
164
221
  'LAP-EL551S-WUS': {
165
222
  module: 'VeSyncAirBaseV2',
166
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
167
- levels: [1, 2, 3]
223
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
224
+ levels: [1, 2, 3],
225
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
226
+ autoPreferences: ['default', 'efficient', 'quiet']
168
227
  },
169
228
  // LTF Series
170
229
  'LTF-F422S-KEU': {
@@ -311,11 +370,25 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
311
370
  this.speed_dict = {};
312
371
  this._timer = null;
313
372
  this.details = details;
314
- this.config = exports.fanConfig[this.deviceType] || {
315
- module: 'VeSyncAirBypass',
316
- features: [],
317
- levels: []
318
- };
373
+ const entry = exports.fanConfig[this.deviceType];
374
+ if (entry) {
375
+ this.config = {
376
+ ...entry,
377
+ features: [...entry.features],
378
+ levels: entry.levels ? [...entry.levels] : undefined,
379
+ modes: entry.modes ? [...entry.modes] : undefined,
380
+ autoPreferences: entry.autoPreferences ? [...entry.autoPreferences] : undefined
381
+ };
382
+ }
383
+ else {
384
+ this.config = {
385
+ module: 'VeSyncAirBypass',
386
+ features: [],
387
+ levels: [],
388
+ modes: [],
389
+ autoPreferences: []
390
+ };
391
+ }
319
392
  }
320
393
  /**
321
394
  * Check if feature is supported
@@ -323,6 +396,36 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
323
396
  hasFeature(feature) {
324
397
  return this.config.features.includes(feature);
325
398
  }
399
+ /**
400
+ * Get configured modes for the device, falling back to provided defaults.
401
+ */
402
+ getConfigModes(defaultModes = []) {
403
+ if (Array.isArray(this.config.modes) && this.config.modes.length > 0) {
404
+ return this.config.modes;
405
+ }
406
+ return defaultModes;
407
+ }
408
+ /**
409
+ * Get configured auto preferences for the device, falling back to provided defaults.
410
+ */
411
+ getConfigAutoPreferences(defaultPreferences = []) {
412
+ if (Array.isArray(this.config.autoPreferences) && this.config.autoPreferences.length > 0) {
413
+ return this.config.autoPreferences;
414
+ }
415
+ return defaultPreferences;
416
+ }
417
+ /**
418
+ * Public accessor for supported modes for consumers that need to inspect capability metadata.
419
+ */
420
+ getSupportedModes(defaultModes = []) {
421
+ return this.getConfigModes(defaultModes);
422
+ }
423
+ /**
424
+ * Public accessor for supported auto preference profiles.
425
+ */
426
+ getSupportedAutoPreferences(defaultPreferences = []) {
427
+ return this.getConfigAutoPreferences(defaultPreferences);
428
+ }
326
429
  /**
327
430
  * Check if feature is supported in current mode
328
431
  */
@@ -355,7 +458,10 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
355
458
  * Get maximum fan speed level
356
459
  */
357
460
  getMaxFanSpeed() {
358
- return this.config.levels ? Math.max(...this.config.levels) : 3;
461
+ if (this.config.levels && this.config.levels.length > 0) {
462
+ return Math.max(...this.config.levels);
463
+ }
464
+ return 3;
359
465
  }
360
466
  /**
361
467
  * Get current mode
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.3.13",
3
+ "version": "1.4.0",
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",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "clean": "rm -rf dist",
10
+ "test:everest": "ts-node test/everest-air-parity.test.ts",
10
11
  "prepare": "npm run build",
11
12
  "version": "git add -A src",
12
13
  "postversion": "git push && git push --tags"
@@ -10,7 +10,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
10
10
  protected _lightDetection: boolean = false;
11
11
  protected _lightDetectionState: boolean = false;
12
12
  protected setSpeedLevel: number | null = null;
13
- protected autoPreferences: string[] = ['default', 'efficient', 'quiet'];
13
+ protected autoPreferences: string[];
14
14
  protected enabled: boolean = false;
15
15
  protected _mode: string = '';
16
16
  protected _speed: number = 0;
@@ -18,6 +18,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
18
18
 
19
19
  constructor(details: Record<string, any>, manager: VeSync) {
20
20
  super(details, manager);
21
+ this.autoPreferences = [...this.getConfigAutoPreferences(['default', 'efficient', 'quiet'])];
21
22
  logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
22
23
  }
23
24
 
@@ -310,24 +311,41 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
310
311
  * Override setMode to send workMode payloads for bypassV2 purifiers.
311
312
  */
312
313
  override async setMode(mode: string): Promise<boolean> {
313
- if (!this.modes.includes(mode as any)) {
314
+ const targetMode = mode.toLowerCase();
315
+ const supportedModes = this.modes.map(m => m.toLowerCase());
316
+
317
+ if (!supportedModes.includes(targetMode)) {
314
318
  const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
315
319
  logger.error(`${error} for device: ${this.deviceName}`);
316
320
  throw new Error(error);
317
321
  }
318
322
 
319
323
  // Special handling for Core200S stays the same for all models
320
- if (this.deviceType.includes('Core200S') && mode === 'auto') {
324
+ if (this.deviceType.includes('Core200S') && targetMode === 'auto') {
321
325
  logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
322
326
  return this.setMode('manual');
323
327
  }
324
328
 
329
+ // Manual mode should leverage changeFanSpeed to mirror pyvesync behaviour
330
+ if (targetMode === 'manual') {
331
+ const defaultLevel = this.config.levels && this.config.levels.length > 0
332
+ ? this.config.levels[0]
333
+ : 1;
334
+ const desiredLevel = this.details.manualSpeedLevel ?? this.speed ?? defaultLevel;
335
+ const success = await this.changeFanSpeed(desiredLevel);
336
+ if (success) {
337
+ this.details.mode = 'manual';
338
+ this.mode = 'manual';
339
+ }
340
+ return success;
341
+ }
342
+
325
343
  // BypassV2 purifiers need the workMode payload; other models use the base implementation
326
344
  if (this.requiresPowerSwitchPayload()) {
327
345
  logger.debug(`Using bypassV2 mode payload for ${this.deviceName}`);
328
346
  const [head, body] = this.buildApiDict('setPurifierMode');
329
347
  body.payload.data = {
330
- workMode: mode
348
+ workMode: targetMode
331
349
  };
332
350
 
333
351
  const [response, status] = await this.callApi(
@@ -339,8 +357,26 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
339
357
 
340
358
  const success = this.checkV2Response(response, status, 'setMode');
341
359
  if (success) {
342
- this.details.mode = mode;
343
- this._mode = mode;
360
+ this.details.mode = targetMode;
361
+ this.mode = targetMode;
362
+
363
+ // Update cached speed snapshot for special modes
364
+ if (targetMode === 'sleep') {
365
+ this.speed = 0;
366
+ this.details.speed = 0;
367
+ } else if (targetMode === 'turbo') {
368
+ const maxSpeed = this.config.levels && this.config.levels.length > 0
369
+ ? Math.max(...this.config.levels)
370
+ : this.speed;
371
+ if (typeof maxSpeed === 'number' && maxSpeed > 0) {
372
+ this.speed = maxSpeed;
373
+ this.details.speed = maxSpeed;
374
+ }
375
+ } else if (targetMode === 'auto') {
376
+ this.speed = 0;
377
+ this.details.speed = 0;
378
+ this.details.isAutoSentinel = true;
379
+ }
344
380
 
345
381
  // Force a details update to sync state
346
382
  setTimeout(() => {
@@ -351,7 +387,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
351
387
 
352
388
  return true;
353
389
  } else {
354
- logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
390
+ logger.error(`Failed to set mode to ${targetMode} for device: ${this.deviceName}`);
355
391
  return false;
356
392
  }
357
393
  } else {
@@ -360,6 +396,28 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
360
396
  }
361
397
  }
362
398
 
399
+ /**
400
+ * Convenience helper to enter turbo mode when supported.
401
+ */
402
+ async turboMode(): Promise<boolean> {
403
+ if (!this.modes.map(m => m.toLowerCase()).includes('turbo')) {
404
+ logger.debug(`Turbo mode not supported for device: ${this.deviceName}`);
405
+ return false;
406
+ }
407
+ return this.setMode('turbo');
408
+ }
409
+
410
+ /**
411
+ * Convenience helper to enter pet mode when supported.
412
+ */
413
+ async petMode(): Promise<boolean> {
414
+ if (!this.modes.map(m => m.toLowerCase()).includes('pet')) {
415
+ logger.debug(`Pet mode not supported for device: ${this.deviceName}`);
416
+ return false;
417
+ }
418
+ return this.setMode('pet');
419
+ }
420
+
363
421
  /**
364
422
  * Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
365
423
  */
@@ -395,6 +453,10 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
395
453
  if (success) {
396
454
  this.speed = speed;
397
455
  this.details.manualSpeedLevel = speed;
456
+ this.details.speed = speed;
457
+ this.details.mode = 'manual';
458
+ this.mode = 'manual';
459
+ this.details.isAutoSentinel = false;
398
460
 
399
461
  // Force a details update to sync state
400
462
  setTimeout(() => {
@@ -428,6 +490,10 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
428
490
  if (success) {
429
491
  this.speed = speed;
430
492
  this.details.manualSpeedLevel = speed;
493
+ this.details.speed = speed;
494
+ this.details.mode = 'manual';
495
+ this.mode = 'manual';
496
+ this.details.isAutoSentinel = false;
431
497
  return true;
432
498
  } else {
433
499
  logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
@@ -441,7 +507,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
441
507
  */
442
508
  async setAutoPreference(preference: string = 'default', roomSize: number = 600): Promise<boolean> {
443
509
  if (!this.autoPreferences.includes(preference)) {
444
- logger.debug(`Invalid preference: ${preference} - valid preferences are default, efficient, quiet`);
510
+ logger.debug(`Invalid preference: ${preference} - valid preferences are ${this.autoPreferences.join(', ')}`);
445
511
  return false;
446
512
  }
447
513
 
@@ -7,7 +7,9 @@ import { logger } from '../logger';
7
7
  * VeSync Air Purifier with Bypass
8
8
  */
9
9
  export class VeSyncAirBypass extends VeSyncFan {
10
- protected readonly modes = ['auto', 'manual', 'sleep'] as const;
10
+ protected get modes(): ReadonlyArray<string> {
11
+ return this.getConfigModes(['auto', 'manual', 'sleep']);
12
+ }
11
13
  protected readonly displayModes = ['on', 'off'] as const;
12
14
  protected readonly childLockModes = ['on', 'off'] as const;
13
15
 
@@ -216,16 +218,20 @@ export class VeSyncAirBypass extends VeSyncFan {
216
218
  * Set device mode
217
219
  */
218
220
  async setMode(mode: string): Promise<boolean> {
219
- if (!this.modes.includes(mode as any)) {
221
+ const requested = mode.toLowerCase();
222
+ const supported = this.modes.map(m => m.toLowerCase());
223
+ const allowsCore200AutoFallback = this.deviceType.includes('Core200S') && requested === 'auto';
224
+
225
+ if (!supported.includes(requested) && !allowsCore200AutoFallback) {
220
226
  const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
221
227
  logger.error(`${error} for device: ${this.deviceName}`);
222
228
  throw new Error(error);
223
229
  }
224
230
 
225
- logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
231
+ logger.debug(`Setting mode to ${requested} for device: ${this.deviceName}`);
226
232
 
227
233
  // Special handling for Core200S auto mode
228
- if (this.deviceType.includes('Core200S') && mode === 'auto') {
234
+ if (allowsCore200AutoFallback) {
229
235
  // Core200S doesn't support auto mode, so we'll set it to manual mode
230
236
  logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
231
237
  return this.setMode('manual');
@@ -241,7 +247,7 @@ export class VeSyncAirBypass extends VeSyncFan {
241
247
  configModule: this.configModule,
242
248
  payload: {
243
249
  data: {
244
- mode
250
+ mode: requested
245
251
  },
246
252
  method: 'setPurifierMode',
247
253
  source: 'APP'
@@ -252,10 +258,10 @@ export class VeSyncAirBypass extends VeSyncFan {
252
258
 
253
259
  const success = this.checkResponse([response, status], 'setMode');
254
260
  if (success) {
255
- this.details.mode = mode;
261
+ this.details.mode = requested;
256
262
  return true;
257
263
  } else {
258
- logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
264
+ logger.error(`Failed to set mode to ${requested} for device: ${this.deviceName}`);
259
265
  return false;
260
266
  }
261
267
  }
@@ -7,12 +7,16 @@ import { VeSync } from './vesync';
7
7
  import { logger } from './logger';
8
8
  import { Helpers } from './helpers';
9
9
 
10
+ interface FanConfigEntry {
11
+ module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
12
+ features: string[];
13
+ levels?: number[];
14
+ modes?: ReadonlyArray<string>;
15
+ autoPreferences?: ReadonlyArray<string>;
16
+ }
17
+
10
18
  interface FanConfig {
11
- [key: string]: {
12
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
13
- features: string[];
14
- levels?: number[];
15
- };
19
+ [key: string]: FanConfigEntry;
16
20
  }
17
21
 
18
22
  // Fan configuration
@@ -21,159 +25,218 @@ export const fanConfig: FanConfig = {
21
25
  'Core200S': {
22
26
  module: 'VeSyncAirBypass',
23
27
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
24
- levels: [1, 2, 3, 4] // 4 levels including sleep mode, matching reference plugin
28
+ levels: [1, 2, 3, 4], // 4 levels including sleep mode, matching reference plugin
29
+ modes: ['sleep', 'manual']
25
30
  },
26
31
  'Core300S': {
27
32
  module: 'VeSyncAirBypass',
28
33
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
29
- levels: [1, 2, 3, 4]
34
+ levels: [1, 2, 3, 4],
35
+ modes: ['sleep', 'manual', 'auto'],
36
+ autoPreferences: ['default', 'efficient', 'quiet']
30
37
  },
31
38
  'Core400S': {
32
39
  module: 'VeSyncAirBypass',
33
40
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
34
- levels: [1, 2, 3, 4]
41
+ levels: [1, 2, 3, 4],
42
+ modes: ['sleep', 'manual', 'auto'],
43
+ autoPreferences: ['default', 'efficient', 'quiet']
35
44
  },
36
45
  'Core600S': {
37
46
  module: 'VeSyncAirBypass',
38
47
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
39
- levels: [1, 2, 3, 4]
48
+ levels: [1, 2, 3, 4],
49
+ modes: ['sleep', 'manual', 'auto'],
50
+ autoPreferences: ['default', 'efficient', 'quiet']
40
51
  },
41
52
 
42
53
  // LAP Series
43
54
  'LAP-C201S-AUSR': {
44
55
  module: 'VeSyncAirBypass',
45
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
46
- levels: [1, 2, 3]
56
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
57
+ levels: [1, 2, 3],
58
+ modes: ['sleep', 'manual']
47
59
  },
48
60
  'LAP-C202S-WUSR': {
49
61
  module: 'VeSyncAirBypass',
50
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
51
- levels: [1, 2, 3]
62
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
63
+ levels: [1, 2, 3],
64
+ modes: ['sleep', 'manual']
52
65
  },
53
66
  'LAP-C301S-WJP': {
54
67
  module: 'VeSyncAirBypass',
55
68
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
56
- levels: [1, 2, 3, 4]
69
+ levels: [1, 2, 3, 4],
70
+ modes: ['sleep', 'manual', 'auto'],
71
+ autoPreferences: ['default', 'efficient', 'quiet']
57
72
  },
58
73
  'LAP-C302S-WUSB': {
59
74
  module: 'VeSyncAirBypass',
60
75
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
61
- levels: [1, 2, 3, 4]
76
+ levels: [1, 2, 3, 4],
77
+ modes: ['sleep', 'manual', 'auto'],
78
+ autoPreferences: ['default', 'efficient', 'quiet']
62
79
  },
63
80
  'LAP-C301S-WAAA': {
64
81
  module: 'VeSyncAirBypass',
65
82
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
66
- levels: [1, 2, 3, 4]
83
+ levels: [1, 2, 3, 4],
84
+ modes: ['sleep', 'manual', 'auto'],
85
+ autoPreferences: ['default', 'efficient', 'quiet']
67
86
  },
68
87
  'LAP-C401S-WJP': {
69
88
  module: 'VeSyncAirBypass',
70
89
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
71
- levels: [1, 2, 3, 4]
90
+ levels: [1, 2, 3, 4],
91
+ modes: ['sleep', 'manual', 'auto'],
92
+ autoPreferences: ['default', 'efficient', 'quiet']
72
93
  },
73
94
  'LAP-C401S-WUSR': {
74
95
  module: 'VeSyncAirBypass',
75
96
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
76
- levels: [1, 2, 3, 4]
97
+ levels: [1, 2, 3, 4],
98
+ modes: ['sleep', 'manual', 'auto'],
99
+ autoPreferences: ['default', 'efficient', 'quiet']
77
100
  },
78
101
  'LAP-C401S-WAAA': {
79
102
  module: 'VeSyncAirBypass',
80
103
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
81
- levels: [1, 2, 3, 4]
104
+ levels: [1, 2, 3, 4],
105
+ modes: ['sleep', 'manual', 'auto'],
106
+ autoPreferences: ['default', 'efficient', 'quiet']
82
107
  },
83
108
  'LAP-C601S-WUS': {
84
109
  module: 'VeSyncAirBypass',
85
110
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
86
- levels: [1, 2, 3, 4]
111
+ levels: [1, 2, 3, 4],
112
+ modes: ['sleep', 'manual', 'auto'],
113
+ autoPreferences: ['default', 'efficient', 'quiet']
87
114
  },
88
115
  'LAP-C601S-WUSR': {
89
116
  module: 'VeSyncAirBypass',
90
117
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
91
- levels: [1, 2, 3, 4]
118
+ levels: [1, 2, 3, 4],
119
+ modes: ['sleep', 'manual', 'auto'],
120
+ autoPreferences: ['default', 'efficient', 'quiet']
92
121
  },
93
122
  'LAP-C601S-WEU': {
94
123
  module: 'VeSyncAirBypass',
95
124
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
96
- levels: [1, 2, 3, 4]
125
+ levels: [1, 2, 3, 4],
126
+ modes: ['sleep', 'manual', 'auto'],
127
+ autoPreferences: ['default', 'efficient', 'quiet']
97
128
  },
98
129
  'LAP-V102S-AASR': {
99
130
  module: 'VeSyncAirBaseV2',
100
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
101
- levels: [1, 2, 3, 4]
131
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
132
+ levels: [1, 2, 3, 4],
133
+ modes: ['sleep', 'manual', 'auto', 'pet'],
134
+ autoPreferences: ['default', 'efficient', 'quiet']
102
135
  },
103
136
  'LAP-V102S-WUS': {
104
137
  module: 'VeSyncAirBaseV2',
105
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
106
- levels: [1, 2, 3, 4]
138
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
139
+ levels: [1, 2, 3, 4],
140
+ modes: ['sleep', 'manual', 'auto', 'pet'],
141
+ autoPreferences: ['default', 'efficient', 'quiet']
107
142
  },
108
143
  'LAP-V102S-WEU': {
109
144
  module: 'VeSyncAirBaseV2',
110
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
111
- levels: [1, 2, 3, 4]
145
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
146
+ levels: [1, 2, 3, 4],
147
+ modes: ['sleep', 'manual', 'auto', 'pet'],
148
+ autoPreferences: ['default', 'efficient', 'quiet']
112
149
  },
113
150
  'LAP-V102S-AUSR': {
114
151
  module: 'VeSyncAirBaseV2',
115
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
116
- levels: [1, 2, 3, 4]
152
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
153
+ levels: [1, 2, 3, 4],
154
+ modes: ['sleep', 'manual', 'auto', 'pet'],
155
+ autoPreferences: ['default', 'efficient', 'quiet']
117
156
  },
118
157
  'LAP-V102S-WJP': {
119
158
  module: 'VeSyncAirBaseV2',
120
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
121
- levels: [1, 2, 3, 4]
159
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
160
+ levels: [1, 2, 3, 4],
161
+ modes: ['sleep', 'manual', 'auto', 'pet'],
162
+ autoPreferences: ['default', 'efficient', 'quiet']
122
163
  },
123
164
  'LAP-V201S-AASR': {
124
165
  module: 'VeSyncAirBaseV2',
125
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
126
- levels: [1, 2, 3, 4]
166
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
167
+ levels: [1, 2, 3, 4],
168
+ modes: ['sleep', 'manual', 'auto', 'pet'],
169
+ autoPreferences: ['default', 'efficient', 'quiet']
127
170
  },
128
171
  'LAP-V201S-WJP': {
129
172
  module: 'VeSyncAirBaseV2',
130
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
131
- levels: [1, 2, 3, 4]
173
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
174
+ levels: [1, 2, 3, 4],
175
+ modes: ['sleep', 'manual', 'auto', 'pet'],
176
+ autoPreferences: ['default', 'efficient', 'quiet']
132
177
  },
133
178
  'LAP-V201S-WEU': {
134
179
  module: 'VeSyncAirBaseV2',
135
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
136
- levels: [1, 2, 3, 4]
180
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
181
+ levels: [1, 2, 3, 4],
182
+ modes: ['sleep', 'manual', 'auto', 'pet'],
183
+ autoPreferences: ['default', 'efficient', 'quiet']
137
184
  },
138
185
  'LAP-V201S-WUS': {
139
186
  module: 'VeSyncAirBaseV2',
140
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
141
- levels: [1, 2, 3, 4]
187
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
188
+ levels: [1, 2, 3, 4],
189
+ modes: ['sleep', 'manual', 'auto', 'pet'],
190
+ autoPreferences: ['default', 'efficient', 'quiet']
142
191
  },
143
192
  'LAP-V201-AUSR': {
144
193
  module: 'VeSyncAirBaseV2',
145
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
146
- levels: [1, 2, 3, 4]
194
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
195
+ levels: [1, 2, 3, 4],
196
+ modes: ['sleep', 'manual', 'auto', 'pet'],
197
+ autoPreferences: ['default', 'efficient', 'quiet']
147
198
  },
148
199
  'LAP-V201S-AUSR': {
149
200
  module: 'VeSyncAirBaseV2',
150
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
151
- levels: [1, 2, 3, 4]
201
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
202
+ levels: [1, 2, 3, 4],
203
+ modes: ['sleep', 'manual', 'auto', 'pet'],
204
+ autoPreferences: ['default', 'efficient', 'quiet']
152
205
  },
153
206
  'LAP-V201S-AEUR': {
154
207
  module: 'VeSyncAirBaseV2',
155
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
156
- levels: [1, 2, 3, 4]
208
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'sleep_mode', 'pet_mode', 'filter_life'],
209
+ levels: [1, 2, 3, 4],
210
+ modes: ['sleep', 'manual', 'auto', 'pet'],
211
+ autoPreferences: ['default', 'efficient', 'quiet']
157
212
  },
158
213
  'LAP-EL551S-AUS': {
159
214
  module: 'VeSyncAirBaseV2',
160
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
161
- levels: [1, 2, 3]
215
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
216
+ levels: [1, 2, 3],
217
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
218
+ autoPreferences: ['default', 'efficient', 'quiet']
162
219
  },
163
220
  'LAP-EL551S-AEUR': {
164
221
  module: 'VeSyncAirBaseV2',
165
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
166
- levels: [1, 2, 3]
222
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
223
+ levels: [1, 2, 3],
224
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
225
+ autoPreferences: ['default', 'efficient', 'quiet']
167
226
  },
168
227
  'LAP-EL551S-WEU': {
169
228
  module: 'VeSyncAirBaseV2',
170
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
171
- levels: [1, 2, 3]
229
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
230
+ levels: [1, 2, 3],
231
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
232
+ autoPreferences: ['default', 'efficient', 'quiet']
172
233
  },
173
234
  'LAP-EL551S-WUS': {
174
235
  module: 'VeSyncAirBaseV2',
175
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
176
- levels: [1, 2, 3]
236
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'sleep_mode', 'turbo_mode', 'light_detection', 'filter_life'],
237
+ levels: [1, 2, 3],
238
+ modes: ['sleep', 'manual', 'auto', 'turbo'],
239
+ autoPreferences: ['default', 'efficient', 'quiet']
177
240
  },
178
241
 
179
242
  // LTF Series
@@ -342,20 +405,29 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
342
405
  protected mode_dict: Record<string, any> = {};
343
406
  protected speed_dict: Record<string, any> = {};
344
407
  protected _timer: number | { duration: number; action: string } | null = null;
345
- protected config: {
346
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
347
- features: string[];
348
- levels?: number[];
349
- };
408
+ protected config: FanConfigEntry;
350
409
 
351
410
  constructor(details: Record<string, any>, manager: VeSync) {
352
411
  super(details, manager);
353
412
  this.details = details;
354
- this.config = fanConfig[this.deviceType] || {
355
- module: 'VeSyncAirBypass',
356
- features: [],
357
- levels: []
358
- };
413
+ const entry = fanConfig[this.deviceType];
414
+ if (entry) {
415
+ this.config = {
416
+ ...entry,
417
+ features: [...entry.features],
418
+ levels: entry.levels ? [...entry.levels] : undefined,
419
+ modes: entry.modes ? [...entry.modes] : undefined,
420
+ autoPreferences: entry.autoPreferences ? [...entry.autoPreferences] : undefined
421
+ };
422
+ } else {
423
+ this.config = {
424
+ module: 'VeSyncAirBypass',
425
+ features: [],
426
+ levels: [],
427
+ modes: [],
428
+ autoPreferences: []
429
+ };
430
+ }
359
431
  }
360
432
 
361
433
  /**
@@ -365,6 +437,40 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
365
437
  return this.config.features.includes(feature);
366
438
  }
367
439
 
440
+ /**
441
+ * Get configured modes for the device, falling back to provided defaults.
442
+ */
443
+ protected getConfigModes(defaultModes: ReadonlyArray<string> = []): ReadonlyArray<string> {
444
+ if (Array.isArray(this.config.modes) && this.config.modes.length > 0) {
445
+ return this.config.modes;
446
+ }
447
+ return defaultModes;
448
+ }
449
+
450
+ /**
451
+ * Get configured auto preferences for the device, falling back to provided defaults.
452
+ */
453
+ protected getConfigAutoPreferences(defaultPreferences: ReadonlyArray<string> = []): ReadonlyArray<string> {
454
+ if (Array.isArray(this.config.autoPreferences) && this.config.autoPreferences.length > 0) {
455
+ return this.config.autoPreferences;
456
+ }
457
+ return defaultPreferences;
458
+ }
459
+
460
+ /**
461
+ * Public accessor for supported modes for consumers that need to inspect capability metadata.
462
+ */
463
+ public getSupportedModes(defaultModes: ReadonlyArray<string> = []): ReadonlyArray<string> {
464
+ return this.getConfigModes(defaultModes);
465
+ }
466
+
467
+ /**
468
+ * Public accessor for supported auto preference profiles.
469
+ */
470
+ public getSupportedAutoPreferences(defaultPreferences: ReadonlyArray<string> = []): ReadonlyArray<string> {
471
+ return this.getConfigAutoPreferences(defaultPreferences);
472
+ }
473
+
368
474
  /**
369
475
  * Check if feature is supported in current mode
370
476
  */
@@ -403,7 +509,10 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
403
509
  * Get maximum fan speed level
404
510
  */
405
511
  getMaxFanSpeed(): number {
406
- return this.config.levels ? Math.max(...this.config.levels) : 3;
512
+ if (this.config.levels && this.config.levels.length > 0) {
513
+ return Math.max(...this.config.levels);
514
+ }
515
+ return 3;
407
516
  }
408
517
 
409
518
  /**