tsvesync 1.0.89 → 1.0.91

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.
@@ -375,7 +375,14 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
375
375
  */
376
376
  get filterLife() {
377
377
  var _a;
378
- return (_a = this.details.filter_life) !== null && _a !== void 0 ? _a : 0;
378
+ // Handle different filter_life formats from different devices
379
+ const filterLife = this.details.filter_life;
380
+ // For Air131 devices, filter_life is an object with percent property
381
+ if (typeof filterLife === 'object' && filterLife !== null && 'percent' in filterLife) {
382
+ return (_a = filterLife.percent) !== null && _a !== void 0 ? _a : 0;
383
+ }
384
+ // For other devices, filter_life is a direct number
385
+ return typeof filterLife === 'number' ? filterLife : 0;
379
386
  }
380
387
  /**
381
388
  * Get screen status
@@ -392,12 +399,46 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
392
399
  return (_a = this.details.child_lock) !== null && _a !== void 0 ? _a : false;
393
400
  }
394
401
  /**
395
- * Get air quality (if supported)
402
+ * Get air quality level (if supported)
396
403
  */
397
- get airQuality() {
404
+ get airQualityLevel() {
398
405
  var _a;
399
406
  return (_a = this.details.air_quality) !== null && _a !== void 0 ? _a : '';
400
407
  }
408
+ /**
409
+ * Get air quality (alias for airQualityLevel for backward compatibility)
410
+ */
411
+ get airQuality() {
412
+ return this.airQualityLevel;
413
+ }
414
+ /**
415
+ * Get air quality value - PM2.5 in μg/m³ (if supported)
416
+ */
417
+ get airQualityValue() {
418
+ var _a;
419
+ return (_a = this.details.air_quality_value) !== null && _a !== void 0 ? _a : 0;
420
+ }
421
+ /**
422
+ * Get PM1 value (if supported)
423
+ */
424
+ get pm1() {
425
+ var _a;
426
+ return (_a = this.details.pm1) !== null && _a !== void 0 ? _a : 0;
427
+ }
428
+ /**
429
+ * Get PM10 value (if supported)
430
+ */
431
+ get pm10() {
432
+ var _a;
433
+ return (_a = this.details.pm10) !== null && _a !== void 0 ? _a : 0;
434
+ }
435
+ /**
436
+ * Get air quality percentage (if supported)
437
+ */
438
+ get aqPercent() {
439
+ var _a;
440
+ return (_a = this.details.aq_percent) !== null && _a !== void 0 ? _a : 0;
441
+ }
401
442
  /**
402
443
  * Get humidity level (if supported)
403
444
  */
@@ -445,7 +486,19 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
445
486
  info.push(['Child Lock: ', this.childLock ? 'Enabled' : 'Disabled']);
446
487
  }
447
488
  if (this.hasFeature('air_quality')) {
448
- info.push(['Air Quality: ', this.airQuality]);
489
+ info.push(['Air Quality Level: ', this.airQualityLevel]);
490
+ if (this.airQualityValue > 0) {
491
+ info.push(['Air Quality Value: ', this.airQualityValue, ' μg/m³']);
492
+ }
493
+ if (this.pm1 > 0) {
494
+ info.push(['PM1: ', this.pm1, ' μg/m³']);
495
+ }
496
+ if (this.pm10 > 0) {
497
+ info.push(['PM10: ', this.pm10, ' μg/m³']);
498
+ }
499
+ if (this.aqPercent > 0) {
500
+ info.push(['AQ Percent: ', this.aqPercent, '%']);
501
+ }
449
502
  }
450
503
  if (this.hasFeature('humidity')) {
451
504
  info.push(['Humidity: ', this.humidity, '%']);
@@ -486,7 +539,19 @@ class VeSyncFan extends vesyncBaseDevice_1.VeSyncBaseDevice {
486
539
  details['Child Lock'] = this.childLock ? 'Enabled' : 'Disabled';
487
540
  }
488
541
  if (this.hasFeature('air_quality')) {
489
- details['Air Quality'] = this.airQuality;
542
+ details['Air Quality Level'] = this.airQualityLevel.toString();
543
+ if (this.airQualityValue > 0) {
544
+ details['Air Quality Value'] = this.airQualityValue.toString();
545
+ }
546
+ if (this.pm1 > 0) {
547
+ details['PM1'] = this.pm1.toString();
548
+ }
549
+ if (this.pm10 > 0) {
550
+ details['PM10'] = this.pm10.toString();
551
+ }
552
+ if (this.aqPercent > 0) {
553
+ details['AQ Percent'] = this.aqPercent.toString();
554
+ }
490
555
  }
491
556
  if (this.hasFeature('humidity')) {
492
557
  details['Humidity'] = this.humidity.toString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.0.89",
3
+ "version": "1.0.91",
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",
@@ -67,10 +67,11 @@ export class VeSyncAir131 extends VeSyncFan {
67
67
  this.details = {
68
68
  mode: data.mode || 'manual', // Default to manual if not specified
69
69
  speed: data.level || 0,
70
- filterLife: data.filterLife || 0,
71
- screenStatus: data.screenStatus || 'off',
72
- childLock: data.childLock || false,
73
- airQuality: data.airQuality || 'unknown',
70
+ filter_life: data.filterLife || data.filter_life || 0,
71
+ screen_status: data.screenStatus || 'off',
72
+ child_lock: data.childLock || false,
73
+ air_quality: data.airQuality || 'unknown',
74
+ air_quality_value: data.air_quality_value || 0,
74
75
  active_time: data.activeTime || 0
75
76
  };
76
77
 
@@ -64,12 +64,18 @@ export class VeSyncAirBypass extends VeSyncFan {
64
64
  this.details = {
65
65
  mode: result.mode || '',
66
66
  speed: result.level || 0,
67
- filterLife: result.filter_life || 0,
68
- screenStatus: result.display ? 'on' : 'off',
69
- childLock: result.child_lock || false,
70
- airQuality: result.air_quality || 0
67
+ filter_life: result.filter_life || 0,
68
+ screen_status: result.display ? 'on' : 'off',
69
+ child_lock: result.child_lock || false,
70
+ air_quality: result.air_quality || 0,
71
+ air_quality_value: result.air_quality_value || 0
71
72
  };
72
73
 
74
+ // Capture additional air quality properties if available
75
+ if ('PM1' in result) this.details.pm1 = result.PM1;
76
+ if ('PM10' in result) this.details.pm10 = result.PM10;
77
+ if ('AQPercent' in result) this.details.aq_percent = result.AQPercent;
78
+
73
79
  // Don't overwrite config as it contains features array
74
80
  if (result.configuration) {
75
81
  this.details.configuration = result.configuration;
@@ -7,157 +7,9 @@ import crypto from 'crypto';
7
7
  import { VeSync } from './vesync';
8
8
  import { logger } from './logger';
9
9
 
10
- // API configuration
10
+ // API configuration - Always use US endpoint
11
11
  let _apiBaseUrl = 'https://smartapi.vesync.com';
12
12
 
13
- // Regional API endpoints
14
- export const REGIONAL_ENDPOINTS = {
15
- US: 'https://smartapi.vesync.com',
16
- EU: 'https://smartapi.vesync.eu',
17
- ASIA: 'https://smartapi.vesync.com.cn'
18
- } as const;
19
-
20
- // Timezone to region mapping for API endpoint detection
21
- const TIMEZONE_REGION_MAP: Record<string, keyof typeof REGIONAL_ENDPOINTS> = {
22
- // European timezones - Western Europe
23
- 'Europe/Berlin': 'EU',
24
- 'Europe/London': 'EU',
25
- 'Europe/Paris': 'EU',
26
- 'Europe/Rome': 'EU',
27
- 'Europe/Madrid': 'EU',
28
- 'Europe/Amsterdam': 'EU',
29
- 'Europe/Vienna': 'EU',
30
- 'Europe/Brussels': 'EU',
31
- 'Europe/Copenhagen': 'EU',
32
- 'Europe/Dublin': 'EU',
33
- 'Europe/Helsinki': 'EU',
34
- 'Europe/Lisbon': 'EU',
35
- 'Europe/Luxembourg': 'EU',
36
- 'Europe/Oslo': 'EU',
37
- 'Europe/Prague': 'EU',
38
- 'Europe/Stockholm': 'EU',
39
- 'Europe/Warsaw': 'EU',
40
- 'Europe/Zurich': 'EU',
41
-
42
- // European timezones - Eastern Europe
43
- 'Europe/Athens': 'EU',
44
- 'Europe/Budapest': 'EU',
45
- 'Europe/Bucharest': 'EU',
46
- 'Europe/Sofia': 'EU',
47
- 'Europe/Kiev': 'EU',
48
- 'Europe/Riga': 'EU',
49
- 'Europe/Vilnius': 'EU',
50
- 'Europe/Tallinn': 'EU',
51
- 'Europe/Zagreb': 'EU',
52
- 'Europe/Ljubljana': 'EU',
53
- 'Europe/Bratislava': 'EU',
54
- 'Europe/Belgrade': 'EU',
55
- 'Europe/Sarajevo': 'EU',
56
- 'Europe/Podgorica': 'EU',
57
- 'Europe/Skopje': 'EU',
58
- 'Europe/Tirane': 'EU',
59
-
60
- // European timezones - Nordic/Baltic
61
- 'Europe/Reykjavik': 'EU',
62
- 'Europe/Moscow': 'EU',
63
- 'Europe/Kaliningrad': 'EU',
64
- 'Europe/Minsk': 'EU',
65
-
66
- // European timezones - Other
67
- 'Europe/Malta': 'EU',
68
- 'Europe/Monaco': 'EU',
69
- 'Europe/Vatican': 'EU',
70
- 'Europe/San_Marino': 'EU',
71
- 'Europe/Andorra': 'EU',
72
- 'Europe/Gibraltar': 'EU',
73
- 'Europe/Isle_of_Man': 'EU',
74
- 'Europe/Jersey': 'EU',
75
- 'Europe/Guernsey': 'EU',
76
-
77
- // Asian timezones - East Asia
78
- 'Asia/Shanghai': 'ASIA',
79
- 'Asia/Beijing': 'ASIA',
80
- 'Asia/Hong_Kong': 'ASIA',
81
- 'Asia/Tokyo': 'ASIA',
82
- 'Asia/Seoul': 'ASIA',
83
- 'Asia/Singapore': 'ASIA',
84
- 'Asia/Taipei': 'ASIA',
85
- 'Asia/Macau': 'ASIA',
86
-
87
- // Asian timezones - Southeast Asia
88
- 'Asia/Bangkok': 'ASIA',
89
- 'Asia/Jakarta': 'ASIA',
90
- 'Asia/Manila': 'ASIA',
91
- 'Asia/Ho_Chi_Minh': 'ASIA',
92
- 'Asia/Kuala_Lumpur': 'ASIA',
93
- 'Asia/Phnom_Penh': 'ASIA',
94
- 'Asia/Vientiane': 'ASIA',
95
- 'Asia/Yangon': 'ASIA',
96
- 'Asia/Brunei': 'ASIA',
97
-
98
- // Asian timezones - South Asia
99
- 'Asia/Mumbai': 'ASIA',
100
- 'Asia/Delhi': 'ASIA',
101
- 'Asia/Kolkata': 'ASIA',
102
- 'Asia/Dhaka': 'ASIA',
103
- 'Asia/Karachi': 'ASIA',
104
- 'Asia/Colombo': 'ASIA',
105
- 'Asia/Kathmandu': 'ASIA',
106
- 'Asia/Thimphu': 'ASIA',
107
-
108
- // Asian timezones - Central Asia
109
- 'Asia/Almaty': 'ASIA',
110
- 'Asia/Bishkek': 'ASIA',
111
- 'Asia/Dushanbe': 'ASIA',
112
- 'Asia/Tashkent': 'ASIA',
113
- 'Asia/Ashgabat': 'ASIA',
114
-
115
- // Asian timezones - West Asia / Middle East
116
- 'Asia/Dubai': 'ASIA',
117
- 'Asia/Riyadh': 'ASIA',
118
- 'Asia/Tehran': 'ASIA',
119
- 'Asia/Baghdad': 'ASIA',
120
- 'Asia/Kuwait': 'ASIA',
121
- 'Asia/Qatar': 'ASIA',
122
- 'Asia/Bahrain': 'ASIA',
123
- 'Asia/Muscat': 'ASIA',
124
- 'Asia/Baku': 'ASIA',
125
- 'Asia/Yerevan': 'ASIA',
126
- 'Asia/Tbilisi': 'ASIA',
127
-
128
- // Oceania (using ASIA endpoint for now)
129
- 'Australia/Sydney': 'ASIA',
130
- 'Australia/Melbourne': 'ASIA',
131
- 'Australia/Brisbane': 'ASIA',
132
- 'Australia/Perth': 'ASIA',
133
- 'Australia/Adelaide': 'ASIA',
134
- 'Australia/Hobart': 'ASIA',
135
- 'Australia/Darwin': 'ASIA',
136
- 'Australia/Canberra': 'ASIA',
137
- 'Pacific/Auckland': 'ASIA',
138
- 'Pacific/Wellington': 'ASIA',
139
- 'Pacific/Fiji': 'ASIA',
140
- 'Pacific/Honolulu': 'US',
141
-
142
- // North America (Canada)
143
- 'America/Toronto': 'US',
144
- 'America/Montreal': 'US',
145
- 'America/Vancouver': 'US',
146
- 'America/Calgary': 'US',
147
- 'America/Edmonton': 'US',
148
- 'America/Winnipeg': 'US',
149
- 'America/Halifax': 'US',
150
- 'America/St_Johns': 'US',
151
-
152
- // North America (Mexico)
153
- 'America/Mexico_City': 'US',
154
- 'America/Tijuana': 'US',
155
- 'America/Cancun': 'US',
156
- 'America/Merida': 'US',
157
- 'America/Monterrey': 'US'
158
- // US and other timezones default to US endpoint
159
- };
160
-
161
13
  export function getApiBaseUrl(): string {
162
14
  return _apiBaseUrl;
163
15
  }
@@ -166,24 +18,6 @@ export function setApiBaseUrl(url: string): void {
166
18
  _apiBaseUrl = url;
167
19
  }
168
20
 
169
- /**
170
- * Get regional API endpoint based on timezone
171
- */
172
- export function getRegionalEndpoint(timeZone: string): string {
173
- const region = TIMEZONE_REGION_MAP[timeZone] || 'US';
174
- return REGIONAL_ENDPOINTS[region];
175
- }
176
-
177
- /**
178
- * Set API endpoint based on timezone if not explicitly set
179
- */
180
- export function setRegionalEndpoint(timeZone: string): void {
181
- const regionalUrl = getRegionalEndpoint(timeZone);
182
- if (_apiBaseUrl === REGIONAL_ENDPOINTS.US && regionalUrl !== REGIONAL_ENDPOINTS.US) {
183
- logger.debug(`Setting regional API endpoint for ${timeZone}: ${regionalUrl}`);
184
- setApiBaseUrl(regionalUrl);
185
- }
186
- }
187
21
 
188
22
  export const API_RATE_LIMIT = 30;
189
23
  export const API_TIMEOUT = 15000;
@@ -375,8 +209,8 @@ export class Helpers {
375
209
  try {
376
210
  // Ensure API base URL is properly set
377
211
  if (!_apiBaseUrl || _apiBaseUrl === 'undefined') {
378
- logger.error('API base URL is not properly configured. Setting regional endpoint...');
379
- setRegionalEndpoint(manager.timeZone);
212
+ logger.error('API base URL is not properly configured. Setting to default US endpoint...');
213
+ setApiBaseUrl('https://smartapi.vesync.com');
380
214
  }
381
215
 
382
216
  const url = _apiBaseUrl + endpoint;
package/src/lib/vesync.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * VeSync API Device Library
3
3
  */
4
4
 
5
- import { Helpers, API_RATE_LIMIT, DEFAULT_TZ, setApiBaseUrl, setRegionalEndpoint, getApiBaseUrl } from './helpers';
5
+ import { Helpers, API_RATE_LIMIT, DEFAULT_TZ, setApiBaseUrl, getApiBaseUrl } from './helpers';
6
6
  import { VeSyncBaseDevice } from './vesyncBaseDevice';
7
7
  import { fanModules } from './vesyncFanImpl';
8
8
  import { outletModules } from './vesyncOutletImpl';
@@ -180,7 +180,7 @@ export class VeSync {
180
180
  * Initialize VeSync Manager
181
181
  * @param username - VeSync account username
182
182
  * @param password - VeSync account password
183
- * @param timeZone - Optional timezone (defaults to America/New_York)
183
+ * @param timeZone - Optional timezone for device operations (defaults to America/New_York)
184
184
  * @param debug - Optional debug mode flag
185
185
  * @param redact - Optional redact mode flag
186
186
  * @param apiUrl - Optional API base URL override
@@ -241,12 +241,12 @@ export class VeSync {
241
241
  logger.debug('Time zone is not a string');
242
242
  }
243
243
 
244
- // Set custom API URL if provided, otherwise use regional endpoint
244
+ // Set custom API URL if provided, otherwise use default US endpoint
245
245
  if (apiUrl) {
246
246
  setApiBaseUrl(apiUrl);
247
247
  } else {
248
- // Automatically set regional endpoint based on timezone
249
- setRegionalEndpoint(this.timeZone);
248
+ // Always use US endpoint
249
+ setApiBaseUrl('https://smartapi.vesync.com');
250
250
  }
251
251
 
252
252
  // Set custom logger if provided
@@ -535,7 +535,7 @@ export class VeSync {
535
535
  switch (response.code) {
536
536
  case -11012022:
537
537
  logger.error('App version too low error. Current version:', body.appVersion);
538
- logger.error('This typically indicates a regional API compatibility issue.');
538
+ logger.error('This typically indicates an API version compatibility issue.');
539
539
  break;
540
540
  case -11003:
541
541
  logger.error('Authentication failed - check credentials');
@@ -322,10 +322,14 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
322
322
  protected details: {
323
323
  mode?: string;
324
324
  speed?: number;
325
- filter_life?: number;
325
+ filter_life?: number | { percent: number };
326
326
  screen_status?: 'on' | 'off';
327
327
  child_lock?: boolean;
328
- air_quality?: string;
328
+ air_quality?: string | number;
329
+ air_quality_value?: number;
330
+ pm1?: number;
331
+ pm10?: number;
332
+ aq_percent?: number;
329
333
  humidity?: number;
330
334
  mist_level?: number;
331
335
  warm_level?: number;
@@ -417,7 +421,16 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
417
421
  * Get filter life percentage
418
422
  */
419
423
  get filterLife(): number {
420
- return this.details.filter_life ?? 0;
424
+ // Handle different filter_life formats from different devices
425
+ const filterLife = this.details.filter_life;
426
+
427
+ // For Air131 devices, filter_life is an object with percent property
428
+ if (typeof filterLife === 'object' && filterLife !== null && 'percent' in filterLife) {
429
+ return filterLife.percent ?? 0;
430
+ }
431
+
432
+ // For other devices, filter_life is a direct number
433
+ return typeof filterLife === 'number' ? filterLife : 0;
421
434
  }
422
435
 
423
436
  /**
@@ -435,12 +448,47 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
435
448
  }
436
449
 
437
450
  /**
438
- * Get air quality (if supported)
451
+ * Get air quality level (if supported)
439
452
  */
440
- get airQuality(): string {
453
+ get airQualityLevel(): string | number {
441
454
  return this.details.air_quality ?? '';
442
455
  }
443
456
 
457
+ /**
458
+ * Get air quality (alias for airQualityLevel for backward compatibility)
459
+ */
460
+ get airQuality(): string | number {
461
+ return this.airQualityLevel;
462
+ }
463
+
464
+ /**
465
+ * Get air quality value - PM2.5 in μg/m³ (if supported)
466
+ */
467
+ get airQualityValue(): number {
468
+ return this.details.air_quality_value ?? 0;
469
+ }
470
+
471
+ /**
472
+ * Get PM1 value (if supported)
473
+ */
474
+ get pm1(): number {
475
+ return this.details.pm1 ?? 0;
476
+ }
477
+
478
+ /**
479
+ * Get PM10 value (if supported)
480
+ */
481
+ get pm10(): number {
482
+ return this.details.pm10 ?? 0;
483
+ }
484
+
485
+ /**
486
+ * Get air quality percentage (if supported)
487
+ */
488
+ get aqPercent(): number {
489
+ return this.details.aq_percent ?? 0;
490
+ }
491
+
444
492
  /**
445
493
  * Get humidity level (if supported)
446
494
  */
@@ -491,7 +539,19 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
491
539
  info.push(['Child Lock: ', this.childLock ? 'Enabled' : 'Disabled']);
492
540
  }
493
541
  if (this.hasFeature('air_quality')) {
494
- info.push(['Air Quality: ', this.airQuality]);
542
+ info.push(['Air Quality Level: ', this.airQualityLevel]);
543
+ if (this.airQualityValue > 0) {
544
+ info.push(['Air Quality Value: ', this.airQualityValue, ' μg/m³']);
545
+ }
546
+ if (this.pm1 > 0) {
547
+ info.push(['PM1: ', this.pm1, ' μg/m³']);
548
+ }
549
+ if (this.pm10 > 0) {
550
+ info.push(['PM10: ', this.pm10, ' μg/m³']);
551
+ }
552
+ if (this.aqPercent > 0) {
553
+ info.push(['AQ Percent: ', this.aqPercent, '%']);
554
+ }
495
555
  }
496
556
  if (this.hasFeature('humidity')) {
497
557
  info.push(['Humidity: ', this.humidity, '%']);
@@ -534,7 +594,19 @@ export abstract class VeSyncFan extends VeSyncBaseDevice {
534
594
  details['Child Lock'] = this.childLock ? 'Enabled' : 'Disabled';
535
595
  }
536
596
  if (this.hasFeature('air_quality')) {
537
- details['Air Quality'] = this.airQuality;
597
+ details['Air Quality Level'] = this.airQualityLevel.toString();
598
+ if (this.airQualityValue > 0) {
599
+ details['Air Quality Value'] = this.airQualityValue.toString();
600
+ }
601
+ if (this.pm1 > 0) {
602
+ details['PM1'] = this.pm1.toString();
603
+ }
604
+ if (this.pm10 > 0) {
605
+ details['PM10'] = this.pm10.toString();
606
+ }
607
+ if (this.aqPercent > 0) {
608
+ details['AQ Percent'] = this.aqPercent.toString();
609
+ }
538
610
  }
539
611
  if (this.hasFeature('humidity')) {
540
612
  details['Humidity'] = this.humidity.toString();