tsvesync 1.3.12 → 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.
@@ -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
 
@@ -87,6 +89,12 @@ export class VeSyncAirBypass extends VeSyncFan {
87
89
  air_quality_value: result.air_quality_value || 0
88
90
  };
89
91
 
92
+ const normalizedAirQuality = Helpers.normalizeAirQuality(result.air_quality);
93
+ if (normalizedAirQuality.level >= 1) {
94
+ this.details.air_quality_level = normalizedAirQuality.level;
95
+ }
96
+ this.details.air_quality_label = normalizedAirQuality.label;
97
+
90
98
  // Capture additional air quality properties if available
91
99
  if ('PM1' in result) this.details.pm1 = result.PM1;
92
100
  if ('PM10' in result) this.details.pm10 = result.PM10;
@@ -210,16 +218,20 @@ export class VeSyncAirBypass extends VeSyncFan {
210
218
  * Set device mode
211
219
  */
212
220
  async setMode(mode: string): Promise<boolean> {
213
- 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) {
214
226
  const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
215
227
  logger.error(`${error} for device: ${this.deviceName}`);
216
228
  throw new Error(error);
217
229
  }
218
230
 
219
- logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`);
231
+ logger.debug(`Setting mode to ${requested} for device: ${this.deviceName}`);
220
232
 
221
233
  // Special handling for Core200S auto mode
222
- if (this.deviceType.includes('Core200S') && mode === 'auto') {
234
+ if (allowsCore200AutoFallback) {
223
235
  // Core200S doesn't support auto mode, so we'll set it to manual mode
224
236
  logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
225
237
  return this.setMode('manual');
@@ -235,7 +247,7 @@ export class VeSyncAirBypass extends VeSyncFan {
235
247
  configModule: this.configModule,
236
248
  payload: {
237
249
  data: {
238
- mode
250
+ mode: requested
239
251
  },
240
252
  method: 'setPurifierMode',
241
253
  source: 'APP'
@@ -246,10 +258,10 @@ export class VeSyncAirBypass extends VeSyncFan {
246
258
 
247
259
  const success = this.checkResponse([response, status], 'setMode');
248
260
  if (success) {
249
- this.details.mode = mode;
261
+ this.details.mode = requested;
250
262
  return true;
251
263
  } else {
252
- 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}`);
253
265
  return false;
254
266
  }
255
267
  }
@@ -245,6 +245,52 @@ export class Helpers {
245
245
  return generateTraceId();
246
246
  }
247
247
 
248
+ static normalizeAirQuality(value: unknown): { level: number; label: string } {
249
+ const stringMap: Record<string, number> = {
250
+ 'excellent': 1,
251
+ 'very good': 1,
252
+ 'good': 2,
253
+ 'moderate': 3,
254
+ 'fair': 3,
255
+ 'inferior': 4,
256
+ 'poor': 4,
257
+ 'bad': 4,
258
+ };
259
+
260
+ if (typeof value === 'number') {
261
+ const level = Number.isFinite(value) ? Math.trunc(value) : -1;
262
+ if (level >= 1 && level <= 4) {
263
+ return {
264
+ level,
265
+ label: level === 1
266
+ ? 'excellent'
267
+ : level === 2
268
+ ? 'good'
269
+ : level === 3
270
+ ? 'moderate'
271
+ : 'poor',
272
+ };
273
+ }
274
+ } else if (typeof value === 'string') {
275
+ const normalized = value.trim().toLowerCase();
276
+ const level = stringMap[normalized];
277
+ if (level) {
278
+ return {
279
+ level,
280
+ label: level === 1
281
+ ? 'excellent'
282
+ : level === 2
283
+ ? 'good'
284
+ : level === 3
285
+ ? 'moderate'
286
+ : 'poor',
287
+ };
288
+ }
289
+ }
290
+
291
+ return { level: -1, label: 'unknown' };
292
+ }
293
+
248
294
  /**
249
295
  * Calculate MD5 hash
250
296
  */
@@ -5,13 +5,18 @@
5
5
  import { VeSyncBaseDevice } from './vesyncBaseDevice';
6
6
  import { VeSync } from './vesync';
7
7
  import { logger } from './logger';
8
+ import { Helpers } from './helpers';
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
+ }
8
17
 
9
18
  interface FanConfig {
10
- [key: string]: {
11
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
12
- features: string[];
13
- levels?: number[];
14
- };
19
+ [key: string]: FanConfigEntry;
15
20
  }
16
21
 
17
22
  // Fan configuration
@@ -20,159 +25,218 @@ export const fanConfig: FanConfig = {
20
25
  'Core200S': {
21
26
  module: 'VeSyncAirBypass',
22
27
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
23
- 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']
24
30
  },
25
31
  'Core300S': {
26
32
  module: 'VeSyncAirBypass',
27
33
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
28
- levels: [1, 2, 3, 4]
34
+ levels: [1, 2, 3, 4],
35
+ modes: ['sleep', 'manual', 'auto'],
36
+ autoPreferences: ['default', 'efficient', 'quiet']
29
37
  },
30
38
  'Core400S': {
31
39
  module: 'VeSyncAirBypass',
32
40
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
33
- levels: [1, 2, 3, 4]
41
+ levels: [1, 2, 3, 4],
42
+ modes: ['sleep', 'manual', 'auto'],
43
+ autoPreferences: ['default', 'efficient', 'quiet']
34
44
  },
35
45
  'Core600S': {
36
46
  module: 'VeSyncAirBypass',
37
47
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
38
- levels: [1, 2, 3, 4]
48
+ levels: [1, 2, 3, 4],
49
+ modes: ['sleep', 'manual', 'auto'],
50
+ autoPreferences: ['default', 'efficient', 'quiet']
39
51
  },
40
52
 
41
53
  // LAP Series
42
54
  'LAP-C201S-AUSR': {
43
55
  module: 'VeSyncAirBypass',
44
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
45
- 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']
46
59
  },
47
60
  'LAP-C202S-WUSR': {
48
61
  module: 'VeSyncAirBypass',
49
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
50
- 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']
51
65
  },
52
66
  'LAP-C301S-WJP': {
53
67
  module: 'VeSyncAirBypass',
54
68
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
55
- levels: [1, 2, 3, 4]
69
+ levels: [1, 2, 3, 4],
70
+ modes: ['sleep', 'manual', 'auto'],
71
+ autoPreferences: ['default', 'efficient', 'quiet']
56
72
  },
57
73
  'LAP-C302S-WUSB': {
58
74
  module: 'VeSyncAirBypass',
59
75
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
60
- levels: [1, 2, 3, 4]
76
+ levels: [1, 2, 3, 4],
77
+ modes: ['sleep', 'manual', 'auto'],
78
+ autoPreferences: ['default', 'efficient', 'quiet']
61
79
  },
62
80
  'LAP-C301S-WAAA': {
63
81
  module: 'VeSyncAirBypass',
64
82
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
65
- levels: [1, 2, 3, 4]
83
+ levels: [1, 2, 3, 4],
84
+ modes: ['sleep', 'manual', 'auto'],
85
+ autoPreferences: ['default', 'efficient', 'quiet']
66
86
  },
67
87
  'LAP-C401S-WJP': {
68
88
  module: 'VeSyncAirBypass',
69
89
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
70
- levels: [1, 2, 3, 4]
90
+ levels: [1, 2, 3, 4],
91
+ modes: ['sleep', 'manual', 'auto'],
92
+ autoPreferences: ['default', 'efficient', 'quiet']
71
93
  },
72
94
  'LAP-C401S-WUSR': {
73
95
  module: 'VeSyncAirBypass',
74
96
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
75
- levels: [1, 2, 3, 4]
97
+ levels: [1, 2, 3, 4],
98
+ modes: ['sleep', 'manual', 'auto'],
99
+ autoPreferences: ['default', 'efficient', 'quiet']
76
100
  },
77
101
  'LAP-C401S-WAAA': {
78
102
  module: 'VeSyncAirBypass',
79
103
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
80
- levels: [1, 2, 3, 4]
104
+ levels: [1, 2, 3, 4],
105
+ modes: ['sleep', 'manual', 'auto'],
106
+ autoPreferences: ['default', 'efficient', 'quiet']
81
107
  },
82
108
  'LAP-C601S-WUS': {
83
109
  module: 'VeSyncAirBypass',
84
110
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
85
- levels: [1, 2, 3, 4]
111
+ levels: [1, 2, 3, 4],
112
+ modes: ['sleep', 'manual', 'auto'],
113
+ autoPreferences: ['default', 'efficient', 'quiet']
86
114
  },
87
115
  'LAP-C601S-WUSR': {
88
116
  module: 'VeSyncAirBypass',
89
117
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
90
- levels: [1, 2, 3, 4]
118
+ levels: [1, 2, 3, 4],
119
+ modes: ['sleep', 'manual', 'auto'],
120
+ autoPreferences: ['default', 'efficient', 'quiet']
91
121
  },
92
122
  'LAP-C601S-WEU': {
93
123
  module: 'VeSyncAirBypass',
94
124
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
95
- levels: [1, 2, 3, 4]
125
+ levels: [1, 2, 3, 4],
126
+ modes: ['sleep', 'manual', 'auto'],
127
+ autoPreferences: ['default', 'efficient', 'quiet']
96
128
  },
97
129
  'LAP-V102S-AASR': {
98
130
  module: 'VeSyncAirBaseV2',
99
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
100
- 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']
101
135
  },
102
136
  'LAP-V102S-WUS': {
103
137
  module: 'VeSyncAirBaseV2',
104
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
105
- 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']
106
142
  },
107
143
  'LAP-V102S-WEU': {
108
144
  module: 'VeSyncAirBaseV2',
109
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
110
- 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']
111
149
  },
112
150
  'LAP-V102S-AUSR': {
113
151
  module: 'VeSyncAirBaseV2',
114
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
115
- 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']
116
156
  },
117
157
  'LAP-V102S-WJP': {
118
158
  module: 'VeSyncAirBaseV2',
119
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
120
- 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']
121
163
  },
122
164
  'LAP-V201S-AASR': {
123
165
  module: 'VeSyncAirBaseV2',
124
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
125
- 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']
126
170
  },
127
171
  'LAP-V201S-WJP': {
128
172
  module: 'VeSyncAirBaseV2',
129
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
130
- 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']
131
177
  },
132
178
  'LAP-V201S-WEU': {
133
179
  module: 'VeSyncAirBaseV2',
134
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
135
- 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']
136
184
  },
137
185
  'LAP-V201S-WUS': {
138
186
  module: 'VeSyncAirBaseV2',
139
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
140
- 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']
141
191
  },
142
192
  'LAP-V201-AUSR': {
143
193
  module: 'VeSyncAirBaseV2',
144
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
145
- 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']
146
198
  },
147
199
  'LAP-V201S-AUSR': {
148
200
  module: 'VeSyncAirBaseV2',
149
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
150
- 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']
151
205
  },
152
206
  'LAP-V201S-AEUR': {
153
207
  module: 'VeSyncAirBaseV2',
154
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
155
- 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']
156
212
  },
157
213
  'LAP-EL551S-AUS': {
158
214
  module: 'VeSyncAirBaseV2',
159
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
160
- 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']
161
219
  },
162
220
  'LAP-EL551S-AEUR': {
163
221
  module: 'VeSyncAirBaseV2',
164
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
165
- 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']
166
226
  },
167
227
  'LAP-EL551S-WEU': {
168
228
  module: 'VeSyncAirBaseV2',
169
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
170
- 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']
171
233
  },
172
234
  'LAP-EL551S-WUS': {
173
235
  module: 'VeSyncAirBaseV2',
174
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
175
- 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']
176
240
  },
177
241
 
178
242
  // LTF Series
@@ -327,6 +391,8 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
327
391
  child_lock?: boolean;
328
392
  air_quality?: string | number;
329
393
  air_quality_value?: number;
394
+ air_quality_level?: number;
395
+ air_quality_label?: string;
330
396
  pm1?: number;
331
397
  pm10?: number;
332
398
  aq_percent?: number;
@@ -339,20 +405,29 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
339
405
  protected mode_dict: Record<string, any> = {};
340
406
  protected speed_dict: Record<string, any> = {};
341
407
  protected _timer: number | { duration: number; action: string } | null = null;
342
- protected config: {
343
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan' | 'VeSyncAirBaseV2' | 'VeSyncSuperior6000S' | 'VeSyncHumid1000S' | 'VeSyncHumid200S' | 'VeSyncAir131';
344
- features: string[];
345
- levels?: number[];
346
- };
408
+ protected config: FanConfigEntry;
347
409
 
348
410
  constructor(details: Record<string, any>, manager: VeSync) {
349
411
  super(details, manager);
350
412
  this.details = details;
351
- this.config = fanConfig[this.deviceType] || {
352
- module: 'VeSyncAirBypass',
353
- features: [],
354
- levels: []
355
- };
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
+ }
356
431
  }
357
432
 
358
433
  /**
@@ -362,6 +437,40 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
362
437
  return this.config.features.includes(feature);
363
438
  }
364
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
+
365
474
  /**
366
475
  * Check if feature is supported in current mode
367
476
  */
@@ -400,7 +509,10 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
400
509
  * Get maximum fan speed level
401
510
  */
402
511
  getMaxFanSpeed(): number {
403
- 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;
404
516
  }
405
517
 
406
518
  /**
@@ -468,6 +580,23 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
468
580
  return this.details.air_quality_value ?? 0;
469
581
  }
470
582
 
583
+ /**
584
+ * Normalized air quality metadata aligned with pyvesync levels.
585
+ */
586
+ getNormalizedAirQuality(): { level: number; label: string } {
587
+ if (typeof this.details.air_quality_level === 'number' && this.details.air_quality_level >= 1) {
588
+ const label = this.details.air_quality_label ?? Helpers.normalizeAirQuality(this.details.air_quality_level).label;
589
+ return { level: this.details.air_quality_level, label };
590
+ }
591
+
592
+ const normalized = Helpers.normalizeAirQuality(this.details.air_quality);
593
+ if (normalized.level >= 1) {
594
+ this.details.air_quality_level = normalized.level;
595
+ this.details.air_quality_label = normalized.label;
596
+ }
597
+ return normalized;
598
+ }
599
+
471
600
  /**
472
601
  * Get PM1 value (if supported)
473
602
  */