tsvesync 1.4.0 → 1.4.1

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.
@@ -71,16 +71,30 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
71
71
  logger_1.logger.debug(`${this.deviceName}: API response structure:`, JSON.stringify(result, null, 2));
72
72
  }
73
73
  }
74
+ // Parse air quality with fallback support for multiple API response formats
75
+ // Some devices (like Core 400S) may use PascalCase (AQLevel, PM25)
76
+ // while others use snake_case (air_quality, air_quality_value)
77
+ const airQualityLevel = result.air_quality || result.AQLevel || 0;
78
+ const airQualityValue = result.air_quality_value || result.PM25 || 0;
79
+ // Debug logging to diagnose air quality field availability
80
+ logger_1.logger.debug(`${this.deviceName}: Air quality API response fields:`, {
81
+ air_quality: result.air_quality,
82
+ AQLevel: result.AQLevel,
83
+ air_quality_value: result.air_quality_value,
84
+ PM25: result.PM25,
85
+ parsed_level: airQualityLevel,
86
+ parsed_value: airQualityValue
87
+ });
74
88
  this.details = {
75
89
  mode: result.mode || '',
76
90
  speed: result.level || 0,
77
91
  filter_life: filterLife,
78
92
  screen_status: result.display ? 'on' : 'off',
79
93
  child_lock: result.child_lock || false,
80
- air_quality: result.air_quality || 0,
81
- air_quality_value: result.air_quality_value || 0
94
+ air_quality: airQualityLevel,
95
+ air_quality_value: airQualityValue
82
96
  };
83
- const normalizedAirQuality = helpers_1.Helpers.normalizeAirQuality(result.air_quality);
97
+ const normalizedAirQuality = helpers_1.Helpers.normalizeAirQuality(airQualityLevel);
84
98
  if (normalizedAirQuality.level >= 1) {
85
99
  this.details.air_quality_level = normalizedAirQuality.level;
86
100
  }
@@ -92,6 +106,14 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
92
106
  this.details.pm10 = result.PM10;
93
107
  if ('AQPercent' in result)
94
108
  this.details.aq_percent = result.AQPercent;
109
+ // Debug log additional air quality fields for diagnostics
110
+ if (result.PM1 !== undefined || result.PM10 !== undefined || result.AQPercent !== undefined) {
111
+ logger_1.logger.debug(`${this.deviceName}: Additional air quality data:`, {
112
+ PM1: result.PM1,
113
+ PM10: result.PM10,
114
+ AQPercent: result.AQPercent
115
+ });
116
+ }
95
117
  // Don't overwrite config as it contains features array
96
118
  if (result.configuration) {
97
119
  this.details.configuration = result.configuration;
@@ -47,6 +47,11 @@ export declare abstract class VeSyncFan extends VeSyncBaseDevice {
47
47
  } | null;
48
48
  protected config: FanConfigEntry;
49
49
  constructor(details: Record<string, any>, manager: VeSync);
50
+ /**
51
+ * Find config entry by substring matching for variant device types
52
+ * Maps LAP variants to their base model configs (e.g., LAP-C401S-WUSR → Core400S)
53
+ */
54
+ private findConfigBySubstring;
50
55
  /**
51
56
  * Check if feature is supported
52
57
  */
@@ -52,63 +52,63 @@ exports.fanConfig = {
52
52
  },
53
53
  'LAP-C301S-WJP': {
54
54
  module: 'VeSyncAirBypass',
55
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
55
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
56
56
  levels: [1, 2, 3, 4],
57
57
  modes: ['sleep', 'manual', 'auto'],
58
58
  autoPreferences: ['default', 'efficient', 'quiet']
59
59
  },
60
60
  'LAP-C302S-WUSB': {
61
61
  module: 'VeSyncAirBypass',
62
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
62
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
63
63
  levels: [1, 2, 3, 4],
64
64
  modes: ['sleep', 'manual', 'auto'],
65
65
  autoPreferences: ['default', 'efficient', 'quiet']
66
66
  },
67
67
  'LAP-C301S-WAAA': {
68
68
  module: 'VeSyncAirBypass',
69
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
69
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
70
70
  levels: [1, 2, 3, 4],
71
71
  modes: ['sleep', 'manual', 'auto'],
72
72
  autoPreferences: ['default', 'efficient', 'quiet']
73
73
  },
74
74
  'LAP-C401S-WJP': {
75
75
  module: 'VeSyncAirBypass',
76
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
76
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
77
77
  levels: [1, 2, 3, 4],
78
78
  modes: ['sleep', 'manual', 'auto'],
79
79
  autoPreferences: ['default', 'efficient', 'quiet']
80
80
  },
81
81
  'LAP-C401S-WUSR': {
82
82
  module: 'VeSyncAirBypass',
83
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
83
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
84
84
  levels: [1, 2, 3, 4],
85
85
  modes: ['sleep', 'manual', 'auto'],
86
86
  autoPreferences: ['default', 'efficient', 'quiet']
87
87
  },
88
88
  'LAP-C401S-WAAA': {
89
89
  module: 'VeSyncAirBypass',
90
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
90
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
91
91
  levels: [1, 2, 3, 4],
92
92
  modes: ['sleep', 'manual', 'auto'],
93
93
  autoPreferences: ['default', 'efficient', 'quiet']
94
94
  },
95
95
  'LAP-C601S-WUS': {
96
96
  module: 'VeSyncAirBypass',
97
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
97
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
98
98
  levels: [1, 2, 3, 4],
99
99
  modes: ['sleep', 'manual', 'auto'],
100
100
  autoPreferences: ['default', 'efficient', 'quiet']
101
101
  },
102
102
  'LAP-C601S-WUSR': {
103
103
  module: 'VeSyncAirBypass',
104
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
104
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
105
105
  levels: [1, 2, 3, 4],
106
106
  modes: ['sleep', 'manual', 'auto'],
107
107
  autoPreferences: ['default', 'efficient', 'quiet']
108
108
  },
109
109
  'LAP-C601S-WEU': {
110
110
  module: 'VeSyncAirBypass',
111
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
111
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
112
112
  levels: [1, 2, 3, 4],
113
113
  modes: ['sleep', 'manual', 'auto'],
114
114
  autoPreferences: ['default', 'efficient', 'quiet']
@@ -370,7 +370,13 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
370
370
  this.speed_dict = {};
371
371
  this._timer = null;
372
372
  this.details = details;
373
- const entry = exports.fanConfig[this.deviceType];
373
+ // Try exact match first
374
+ let entry = exports.fanConfig[this.deviceType];
375
+ // If no exact match, try substring matching for LAP variants
376
+ // This handles cases where device reports as "LAP-C401S-WUSR" but we only have "Core400S" config
377
+ if (!entry) {
378
+ entry = this.findConfigBySubstring(this.deviceType);
379
+ }
374
380
  if (entry) {
375
381
  this.config = {
376
382
  ...entry,
@@ -390,6 +396,39 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
390
396
  };
391
397
  }
392
398
  }
399
+ /**
400
+ * Find config entry by substring matching for variant device types
401
+ * Maps LAP variants to their base model configs (e.g., LAP-C401S-WUSR → Core400S)
402
+ */
403
+ findConfigBySubstring(deviceType) {
404
+ // Define model number mappings
405
+ const modelMappings = [
406
+ // Core series air purifiers
407
+ { patterns: ['Core300S', '301S', '302S'], configKey: 'Core300S' },
408
+ { patterns: ['Core400S', '401S'], configKey: 'Core400S' },
409
+ { patterns: ['Core600S', '601S', '602S'], configKey: 'Core600S' },
410
+ { patterns: ['Core200S', '201S', '202S'], configKey: 'Core200S' },
411
+ // Vital series
412
+ { patterns: ['V102S'], configKey: 'LAP-V102S-AASR' },
413
+ { patterns: ['V201S'], configKey: 'LAP-V201S-AASR' },
414
+ // LV-PUR series
415
+ { patterns: ['LV-PUR131S', 'LV-RH131S'], configKey: 'LV-PUR131S' },
416
+ // Everest series
417
+ { patterns: ['LAP-EL551S'], configKey: 'LAP-EL551S-AUS' },
418
+ ];
419
+ // Check each mapping
420
+ for (const mapping of modelMappings) {
421
+ // If device type contains any of the patterns, use the config key
422
+ if (mapping.patterns.some(pattern => deviceType.includes(pattern))) {
423
+ const config = exports.fanConfig[mapping.configKey];
424
+ if (config) {
425
+ logger_1.logger.debug(`${deviceType}: Using fallback config from ${mapping.configKey} via substring match`);
426
+ return config;
427
+ }
428
+ }
429
+ }
430
+ return undefined;
431
+ }
393
432
  /**
394
433
  * Check if feature is supported
395
434
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
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",
@@ -79,17 +79,33 @@ export class VeSyncAirBypass extends VeSyncFan {
79
79
  }
80
80
  }
81
81
 
82
+ // Parse air quality with fallback support for multiple API response formats
83
+ // Some devices (like Core 400S) may use PascalCase (AQLevel, PM25)
84
+ // while others use snake_case (air_quality, air_quality_value)
85
+ const airQualityLevel = result.air_quality || result.AQLevel || 0;
86
+ const airQualityValue = result.air_quality_value || result.PM25 || 0;
87
+
88
+ // Debug logging to diagnose air quality field availability
89
+ logger.debug(`${this.deviceName}: Air quality API response fields:`, {
90
+ air_quality: result.air_quality,
91
+ AQLevel: result.AQLevel,
92
+ air_quality_value: result.air_quality_value,
93
+ PM25: result.PM25,
94
+ parsed_level: airQualityLevel,
95
+ parsed_value: airQualityValue
96
+ });
97
+
82
98
  this.details = {
83
99
  mode: result.mode || '',
84
100
  speed: result.level || 0,
85
101
  filter_life: filterLife,
86
102
  screen_status: result.display ? 'on' : 'off',
87
103
  child_lock: result.child_lock || false,
88
- air_quality: result.air_quality || 0,
89
- air_quality_value: result.air_quality_value || 0
104
+ air_quality: airQualityLevel,
105
+ air_quality_value: airQualityValue
90
106
  };
91
107
 
92
- const normalizedAirQuality = Helpers.normalizeAirQuality(result.air_quality);
108
+ const normalizedAirQuality = Helpers.normalizeAirQuality(airQualityLevel);
93
109
  if (normalizedAirQuality.level >= 1) {
94
110
  this.details.air_quality_level = normalizedAirQuality.level;
95
111
  }
@@ -100,6 +116,15 @@ export class VeSyncAirBypass extends VeSyncFan {
100
116
  if ('PM10' in result) this.details.pm10 = result.PM10;
101
117
  if ('AQPercent' in result) this.details.aq_percent = result.AQPercent;
102
118
 
119
+ // Debug log additional air quality fields for diagnostics
120
+ if (result.PM1 !== undefined || result.PM10 !== undefined || result.AQPercent !== undefined) {
121
+ logger.debug(`${this.deviceName}: Additional air quality data:`, {
122
+ PM1: result.PM1,
123
+ PM10: result.PM10,
124
+ AQPercent: result.AQPercent
125
+ });
126
+ }
127
+
103
128
  // Don't overwrite config as it contains features array
104
129
  if (result.configuration) {
105
130
  this.details.configuration = result.configuration;
@@ -65,63 +65,63 @@ export const fanConfig: FanConfig = {
65
65
  },
66
66
  'LAP-C301S-WJP': {
67
67
  module: 'VeSyncAirBypass',
68
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
68
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
69
69
  levels: [1, 2, 3, 4],
70
70
  modes: ['sleep', 'manual', 'auto'],
71
71
  autoPreferences: ['default', 'efficient', 'quiet']
72
72
  },
73
73
  'LAP-C302S-WUSB': {
74
74
  module: 'VeSyncAirBypass',
75
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
75
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
76
76
  levels: [1, 2, 3, 4],
77
77
  modes: ['sleep', 'manual', 'auto'],
78
78
  autoPreferences: ['default', 'efficient', 'quiet']
79
79
  },
80
80
  'LAP-C301S-WAAA': {
81
81
  module: 'VeSyncAirBypass',
82
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
82
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
83
83
  levels: [1, 2, 3, 4],
84
84
  modes: ['sleep', 'manual', 'auto'],
85
85
  autoPreferences: ['default', 'efficient', 'quiet']
86
86
  },
87
87
  'LAP-C401S-WJP': {
88
88
  module: 'VeSyncAirBypass',
89
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
89
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
90
90
  levels: [1, 2, 3, 4],
91
91
  modes: ['sleep', 'manual', 'auto'],
92
92
  autoPreferences: ['default', 'efficient', 'quiet']
93
93
  },
94
94
  'LAP-C401S-WUSR': {
95
95
  module: 'VeSyncAirBypass',
96
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
96
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
97
97
  levels: [1, 2, 3, 4],
98
98
  modes: ['sleep', 'manual', 'auto'],
99
99
  autoPreferences: ['default', 'efficient', 'quiet']
100
100
  },
101
101
  'LAP-C401S-WAAA': {
102
102
  module: 'VeSyncAirBypass',
103
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
103
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
104
104
  levels: [1, 2, 3, 4],
105
105
  modes: ['sleep', 'manual', 'auto'],
106
106
  autoPreferences: ['default', 'efficient', 'quiet']
107
107
  },
108
108
  'LAP-C601S-WUS': {
109
109
  module: 'VeSyncAirBypass',
110
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
110
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
111
111
  levels: [1, 2, 3, 4],
112
112
  modes: ['sleep', 'manual', 'auto'],
113
113
  autoPreferences: ['default', 'efficient', 'quiet']
114
114
  },
115
115
  'LAP-C601S-WUSR': {
116
116
  module: 'VeSyncAirBypass',
117
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
117
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
118
118
  levels: [1, 2, 3, 4],
119
119
  modes: ['sleep', 'manual', 'auto'],
120
120
  autoPreferences: ['default', 'efficient', 'quiet']
121
121
  },
122
122
  'LAP-C601S-WEU': {
123
123
  module: 'VeSyncAirBypass',
124
- features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
124
+ features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_speed', 'auto_mode', 'sleep_mode', 'filter_life'],
125
125
  levels: [1, 2, 3, 4],
126
126
  modes: ['sleep', 'manual', 'auto'],
127
127
  autoPreferences: ['default', 'efficient', 'quiet']
@@ -410,7 +410,16 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
410
410
  constructor(details: Record<string, any>, manager: VeSync) {
411
411
  super(details, manager);
412
412
  this.details = details;
413
- const entry = fanConfig[this.deviceType];
413
+
414
+ // Try exact match first
415
+ let entry: FanConfigEntry | undefined = fanConfig[this.deviceType];
416
+
417
+ // If no exact match, try substring matching for LAP variants
418
+ // This handles cases where device reports as "LAP-C401S-WUSR" but we only have "Core400S" config
419
+ if (!entry) {
420
+ entry = this.findConfigBySubstring(this.deviceType);
421
+ }
422
+
414
423
  if (entry) {
415
424
  this.config = {
416
425
  ...entry,
@@ -430,6 +439,45 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
430
439
  }
431
440
  }
432
441
 
442
+ /**
443
+ * Find config entry by substring matching for variant device types
444
+ * Maps LAP variants to their base model configs (e.g., LAP-C401S-WUSR → Core400S)
445
+ */
446
+ private findConfigBySubstring(deviceType: string): FanConfigEntry | undefined {
447
+ // Define model number mappings
448
+ const modelMappings: Array<{ patterns: string[]; configKey: string }> = [
449
+ // Core series air purifiers
450
+ { patterns: ['Core300S', '301S', '302S'], configKey: 'Core300S' },
451
+ { patterns: ['Core400S', '401S'], configKey: 'Core400S' },
452
+ { patterns: ['Core600S', '601S', '602S'], configKey: 'Core600S' },
453
+ { patterns: ['Core200S', '201S', '202S'], configKey: 'Core200S' },
454
+
455
+ // Vital series
456
+ { patterns: ['V102S'], configKey: 'LAP-V102S-AASR' },
457
+ { patterns: ['V201S'], configKey: 'LAP-V201S-AASR' },
458
+
459
+ // LV-PUR series
460
+ { patterns: ['LV-PUR131S', 'LV-RH131S'], configKey: 'LV-PUR131S' },
461
+
462
+ // Everest series
463
+ { patterns: ['LAP-EL551S'], configKey: 'LAP-EL551S-AUS' },
464
+ ];
465
+
466
+ // Check each mapping
467
+ for (const mapping of modelMappings) {
468
+ // If device type contains any of the patterns, use the config key
469
+ if (mapping.patterns.some(pattern => deviceType.includes(pattern))) {
470
+ const config = fanConfig[mapping.configKey];
471
+ if (config) {
472
+ logger.debug(`${deviceType}: Using fallback config from ${mapping.configKey} via substring match`);
473
+ return config;
474
+ }
475
+ }
476
+ }
477
+
478
+ return undefined;
479
+ }
480
+
433
481
  /**
434
482
  * Check if feature is supported
435
483
  */