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.
@@ -6,164 +6,224 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.VeSyncFan = exports.fanConfig = void 0;
7
7
  const vesyncBaseDevice_1 = require("./vesyncBaseDevice");
8
8
  const logger_1 = require("./logger");
9
+ const helpers_1 = require("./helpers");
9
10
  // Fan configuration
10
11
  exports.fanConfig = {
11
12
  // Core Series
12
13
  'Core200S': {
13
14
  module: 'VeSyncAirBypass',
14
15
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'sleep_mode', 'filter_life'],
15
- 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']
16
18
  },
17
19
  'Core300S': {
18
20
  module: 'VeSyncAirBypass',
19
21
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
20
- levels: [1, 2, 3, 4]
22
+ levels: [1, 2, 3, 4],
23
+ modes: ['sleep', 'manual', 'auto'],
24
+ autoPreferences: ['default', 'efficient', 'quiet']
21
25
  },
22
26
  'Core400S': {
23
27
  module: 'VeSyncAirBypass',
24
28
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
25
- levels: [1, 2, 3, 4]
29
+ levels: [1, 2, 3, 4],
30
+ modes: ['sleep', 'manual', 'auto'],
31
+ autoPreferences: ['default', 'efficient', 'quiet']
26
32
  },
27
33
  'Core600S': {
28
34
  module: 'VeSyncAirBypass',
29
35
  features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
30
- levels: [1, 2, 3, 4]
36
+ levels: [1, 2, 3, 4],
37
+ modes: ['sleep', 'manual', 'auto'],
38
+ autoPreferences: ['default', 'efficient', 'quiet']
31
39
  },
32
40
  // LAP Series
33
41
  'LAP-C201S-AUSR': {
34
42
  module: 'VeSyncAirBypass',
35
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
36
- 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']
37
46
  },
38
47
  'LAP-C202S-WUSR': {
39
48
  module: 'VeSyncAirBypass',
40
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
41
- 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']
42
52
  },
43
53
  'LAP-C301S-WJP': {
44
54
  module: 'VeSyncAirBypass',
45
55
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
46
- levels: [1, 2, 3, 4]
56
+ levels: [1, 2, 3, 4],
57
+ modes: ['sleep', 'manual', 'auto'],
58
+ autoPreferences: ['default', 'efficient', 'quiet']
47
59
  },
48
60
  'LAP-C302S-WUSB': {
49
61
  module: 'VeSyncAirBypass',
50
62
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
51
- levels: [1, 2, 3, 4]
63
+ levels: [1, 2, 3, 4],
64
+ modes: ['sleep', 'manual', 'auto'],
65
+ autoPreferences: ['default', 'efficient', 'quiet']
52
66
  },
53
67
  'LAP-C301S-WAAA': {
54
68
  module: 'VeSyncAirBypass',
55
69
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
56
- levels: [1, 2, 3, 4]
70
+ levels: [1, 2, 3, 4],
71
+ modes: ['sleep', 'manual', 'auto'],
72
+ autoPreferences: ['default', 'efficient', 'quiet']
57
73
  },
58
74
  'LAP-C401S-WJP': {
59
75
  module: 'VeSyncAirBypass',
60
76
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
61
- levels: [1, 2, 3, 4]
77
+ levels: [1, 2, 3, 4],
78
+ modes: ['sleep', 'manual', 'auto'],
79
+ autoPreferences: ['default', 'efficient', 'quiet']
62
80
  },
63
81
  'LAP-C401S-WUSR': {
64
82
  module: 'VeSyncAirBypass',
65
83
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
66
- levels: [1, 2, 3, 4]
84
+ levels: [1, 2, 3, 4],
85
+ modes: ['sleep', 'manual', 'auto'],
86
+ autoPreferences: ['default', 'efficient', 'quiet']
67
87
  },
68
88
  'LAP-C401S-WAAA': {
69
89
  module: 'VeSyncAirBypass',
70
90
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
71
- levels: [1, 2, 3, 4]
91
+ levels: [1, 2, 3, 4],
92
+ modes: ['sleep', 'manual', 'auto'],
93
+ autoPreferences: ['default', 'efficient', 'quiet']
72
94
  },
73
95
  'LAP-C601S-WUS': {
74
96
  module: 'VeSyncAirBypass',
75
97
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
76
- levels: [1, 2, 3, 4]
98
+ levels: [1, 2, 3, 4],
99
+ modes: ['sleep', 'manual', 'auto'],
100
+ autoPreferences: ['default', 'efficient', 'quiet']
77
101
  },
78
102
  'LAP-C601S-WUSR': {
79
103
  module: 'VeSyncAirBypass',
80
104
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
81
- levels: [1, 2, 3, 4]
105
+ levels: [1, 2, 3, 4],
106
+ modes: ['sleep', 'manual', 'auto'],
107
+ autoPreferences: ['default', 'efficient', 'quiet']
82
108
  },
83
109
  'LAP-C601S-WEU': {
84
110
  module: 'VeSyncAirBypass',
85
111
  features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
86
- levels: [1, 2, 3, 4]
112
+ levels: [1, 2, 3, 4],
113
+ modes: ['sleep', 'manual', 'auto'],
114
+ autoPreferences: ['default', 'efficient', 'quiet']
87
115
  },
88
116
  'LAP-V102S-AASR': {
89
117
  module: 'VeSyncAirBaseV2',
90
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
91
- 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']
92
122
  },
93
123
  'LAP-V102S-WUS': {
94
124
  module: 'VeSyncAirBaseV2',
95
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
96
- 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']
97
129
  },
98
130
  'LAP-V102S-WEU': {
99
131
  module: 'VeSyncAirBaseV2',
100
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
101
- 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']
102
136
  },
103
137
  'LAP-V102S-AUSR': {
104
138
  module: 'VeSyncAirBaseV2',
105
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
106
- 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']
107
143
  },
108
144
  'LAP-V102S-WJP': {
109
145
  module: 'VeSyncAirBaseV2',
110
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'filter_life'],
111
- 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']
112
150
  },
113
151
  'LAP-V201S-AASR': {
114
152
  module: 'VeSyncAirBaseV2',
115
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
116
- 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']
117
157
  },
118
158
  'LAP-V201S-WJP': {
119
159
  module: 'VeSyncAirBaseV2',
120
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'light_detection', 'fan_speed', 'auto_mode', 'filter_life'],
121
- 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']
122
164
  },
123
165
  'LAP-V201S-WEU': {
124
166
  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]
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']
127
171
  },
128
172
  'LAP-V201S-WUS': {
129
173
  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]
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']
132
178
  },
133
179
  'LAP-V201-AUSR': {
134
180
  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]
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']
137
185
  },
138
186
  'LAP-V201S-AUSR': {
139
187
  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]
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']
142
192
  },
143
193
  'LAP-V201S-AEUR': {
144
194
  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]
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']
147
199
  },
148
200
  'LAP-EL551S-AUS': {
149
201
  module: 'VeSyncAirBaseV2',
150
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
151
- 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']
152
206
  },
153
207
  'LAP-EL551S-AEUR': {
154
208
  module: 'VeSyncAirBaseV2',
155
- features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode', 'filter_life'],
156
- 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']
157
213
  },
158
214
  'LAP-EL551S-WEU': {
159
215
  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]
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']
162
220
  },
163
221
  'LAP-EL551S-WUS': {
164
222
  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]
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']
167
227
  },
168
228
  // LTF Series
169
229
  'LTF-F422S-KEU': {
@@ -310,11 +370,25 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
310
370
  this.speed_dict = {};
311
371
  this._timer = null;
312
372
  this.details = details;
313
- this.config = exports.fanConfig[this.deviceType] || {
314
- module: 'VeSyncAirBypass',
315
- features: [],
316
- levels: []
317
- };
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
+ }
318
392
  }
319
393
  /**
320
394
  * Check if feature is supported
@@ -322,6 +396,36 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
322
396
  hasFeature(feature) {
323
397
  return this.config.features.includes(feature);
324
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
+ }
325
429
  /**
326
430
  * Check if feature is supported in current mode
327
431
  */
@@ -354,7 +458,10 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
354
458
  * Get maximum fan speed level
355
459
  */
356
460
  getMaxFanSpeed() {
357
- 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;
358
465
  }
359
466
  /**
360
467
  * Get current mode
@@ -418,6 +525,22 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
418
525
  var _a;
419
526
  return (_a = this.details.air_quality_value) !== null && _a !== void 0 ? _a : 0;
420
527
  }
528
+ /**
529
+ * Normalized air quality metadata aligned with pyvesync levels.
530
+ */
531
+ getNormalizedAirQuality() {
532
+ var _a;
533
+ if (typeof this.details.air_quality_level === 'number' && this.details.air_quality_level >= 1) {
534
+ const label = (_a = this.details.air_quality_label) !== null && _a !== void 0 ? _a : helpers_1.Helpers.normalizeAirQuality(this.details.air_quality_level).label;
535
+ return { level: this.details.air_quality_level, label };
536
+ }
537
+ const normalized = helpers_1.Helpers.normalizeAirQuality(this.details.air_quality);
538
+ if (normalized.level >= 1) {
539
+ this.details.air_quality_level = normalized.level;
540
+ this.details.air_quality_label = normalized.label;
541
+ }
542
+ return normalized;
543
+ }
421
544
  /**
422
545
  * Get PM1 value (if supported)
423
546
  */
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.3.12",
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"
@@ -88,6 +88,8 @@ export class VeSyncAir131 extends VeSyncFan {
88
88
  }
89
89
  }
90
90
 
91
+ const normalizedAirQuality = Helpers.normalizeAirQuality(data.airQuality);
92
+
91
93
  this.details = {
92
94
  mode: data.mode || 'manual', // Default to manual if not specified
93
95
  speed: data.level || 0,
@@ -96,6 +98,8 @@ export class VeSyncAir131 extends VeSyncFan {
96
98
  child_lock: data.childLock || false,
97
99
  air_quality: data.airQuality || 'unknown',
98
100
  air_quality_value: data.air_quality_value || 0,
101
+ air_quality_level: normalizedAirQuality.level >= 1 ? normalizedAirQuality.level : undefined,
102
+ air_quality_label: normalizedAirQuality.label,
99
103
  active_time: data.activeTime || 0
100
104
  };
101
105
 
@@ -1,6 +1,7 @@
1
1
  import { VeSyncAirBypass } from './airBypass';
2
2
  import { VeSync } from '../vesync';
3
3
  import { logger } from '../logger';
4
+ import { Helpers } from '../helpers';
4
5
 
5
6
  /**
6
7
  * VeSync Air Purifier with Bypass V2
@@ -9,7 +10,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
9
10
  protected _lightDetection: boolean = false;
10
11
  protected _lightDetectionState: boolean = false;
11
12
  protected setSpeedLevel: number | null = null;
12
- protected autoPreferences: string[] = ['default', 'efficient', 'quiet'];
13
+ protected autoPreferences: string[];
13
14
  protected enabled: boolean = false;
14
15
  protected _mode: string = '';
15
16
  protected _speed: number = 0;
@@ -17,6 +18,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
17
18
 
18
19
  constructor(details: Record<string, any>, manager: VeSync) {
19
20
  super(details, manager);
21
+ this.autoPreferences = [...this.getConfigAutoPreferences(['default', 'efficient', 'quiet'])];
20
22
  logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
21
23
  }
22
24
 
@@ -189,6 +191,11 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
189
191
  if (this.hasFeature('air_quality')) {
190
192
  this.details.air_quality_value = devDict.PM25 ?? 0;
191
193
  this.details.air_quality = devDict.AQLevel ?? 0;
194
+ const normalizedAirQuality = Helpers.normalizeAirQuality(devDict.AQLevel);
195
+ if (normalizedAirQuality.level >= 1) {
196
+ this.details.air_quality_level = normalizedAirQuality.level;
197
+ }
198
+ this.details.air_quality_label = normalizedAirQuality.label;
192
199
  }
193
200
 
194
201
  if ('PM1' in devDict) this.details.pm1 = devDict.PM1;
@@ -304,24 +311,41 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
304
311
  * Override setMode to send workMode payloads for bypassV2 purifiers.
305
312
  */
306
313
  override async setMode(mode: string): Promise<boolean> {
307
- 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)) {
308
318
  const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
309
319
  logger.error(`${error} for device: ${this.deviceName}`);
310
320
  throw new Error(error);
311
321
  }
312
322
 
313
323
  // Special handling for Core200S stays the same for all models
314
- if (this.deviceType.includes('Core200S') && mode === 'auto') {
324
+ if (this.deviceType.includes('Core200S') && targetMode === 'auto') {
315
325
  logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
316
326
  return this.setMode('manual');
317
327
  }
318
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
+
319
343
  // BypassV2 purifiers need the workMode payload; other models use the base implementation
320
344
  if (this.requiresPowerSwitchPayload()) {
321
345
  logger.debug(`Using bypassV2 mode payload for ${this.deviceName}`);
322
346
  const [head, body] = this.buildApiDict('setPurifierMode');
323
347
  body.payload.data = {
324
- workMode: mode
348
+ workMode: targetMode
325
349
  };
326
350
 
327
351
  const [response, status] = await this.callApi(
@@ -333,8 +357,26 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
333
357
 
334
358
  const success = this.checkV2Response(response, status, 'setMode');
335
359
  if (success) {
336
- this.details.mode = mode;
337
- 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
+ }
338
380
 
339
381
  // Force a details update to sync state
340
382
  setTimeout(() => {
@@ -345,7 +387,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
345
387
 
346
388
  return true;
347
389
  } else {
348
- 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}`);
349
391
  return false;
350
392
  }
351
393
  } else {
@@ -354,6 +396,28 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
354
396
  }
355
397
  }
356
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
+
357
421
  /**
358
422
  * Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
359
423
  */
@@ -389,6 +453,10 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
389
453
  if (success) {
390
454
  this.speed = speed;
391
455
  this.details.manualSpeedLevel = speed;
456
+ this.details.speed = speed;
457
+ this.details.mode = 'manual';
458
+ this.mode = 'manual';
459
+ this.details.isAutoSentinel = false;
392
460
 
393
461
  // Force a details update to sync state
394
462
  setTimeout(() => {
@@ -422,6 +490,10 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
422
490
  if (success) {
423
491
  this.speed = speed;
424
492
  this.details.manualSpeedLevel = speed;
493
+ this.details.speed = speed;
494
+ this.details.mode = 'manual';
495
+ this.mode = 'manual';
496
+ this.details.isAutoSentinel = false;
425
497
  return true;
426
498
  } else {
427
499
  logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
@@ -435,7 +507,7 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
435
507
  */
436
508
  async setAutoPreference(preference: string = 'default', roomSize: number = 600): Promise<boolean> {
437
509
  if (!this.autoPreferences.includes(preference)) {
438
- logger.debug(`Invalid preference: ${preference} - valid preferences are default, efficient, quiet`);
510
+ logger.debug(`Invalid preference: ${preference} - valid preferences are ${this.autoPreferences.join(', ')}`);
439
511
  return false;
440
512
  }
441
513