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.
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for VeSyncFan base class and air quality features
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vesyncFan_1 = require("../lib/vesyncFan");
7
+ // Mock VeSync manager
8
+ const mockManager = {
9
+ username: 'test@example.com',
10
+ password: 'testpass',
11
+ token: 'testtoken',
12
+ accountID: 'testaccount',
13
+ apiKey: 'testapikey',
14
+ apiBase: 'testbase',
15
+ timezone: 'UTC'
16
+ };
17
+ // Test implementation of abstract VeSyncFan class
18
+ class TestVeSyncFan extends vesyncFan_1.VeSyncFan {
19
+ async getDetails() {
20
+ return true;
21
+ }
22
+ async turnOn() {
23
+ this.deviceStatus = 'on';
24
+ return true;
25
+ }
26
+ async turnOff() {
27
+ this.deviceStatus = 'off';
28
+ return true;
29
+ }
30
+ async changeFanSpeed(level) {
31
+ this.details.speed = level;
32
+ return true;
33
+ }
34
+ async changeMode(mode) {
35
+ this.details.mode = mode;
36
+ return true;
37
+ }
38
+ }
39
+ describe('VeSyncFan Base Class', () => {
40
+ let fan;
41
+ beforeEach(() => {
42
+ const deviceDetails = {
43
+ deviceName: 'Test Fan',
44
+ deviceType: 'Core300S',
45
+ cid: 'test-cid',
46
+ uuid: 'test-uuid',
47
+ deviceStatus: 'off',
48
+ deviceRegion: 'US',
49
+ configModule: 'VeSyncAirBypass',
50
+ macId: '00:11:22:33:44:55',
51
+ deviceCategory: 'fan',
52
+ connectionStatus: 'online'
53
+ };
54
+ fan = new TestVeSyncFan(deviceDetails, mockManager);
55
+ });
56
+ describe('Air Quality Getters', () => {
57
+ it('should return default values when air quality data is not available', () => {
58
+ expect(fan.airQualityValue).toBe(0);
59
+ expect(fan.pm1).toBe(0);
60
+ expect(fan.pm10).toBe(0);
61
+ expect(fan.aqPercent).toBe(0);
62
+ expect(fan.airQualityLevel).toBe('');
63
+ });
64
+ it('should return correct air quality values when data is available', () => {
65
+ fan.details.air_quality_value = 25;
66
+ fan.details.pm1 = 15;
67
+ fan.details.pm10 = 30;
68
+ fan.details.aq_percent = 80;
69
+ fan.details.air_quality = 'Good';
70
+ expect(fan.airQualityValue).toBe(25);
71
+ expect(fan.pm1).toBe(15);
72
+ expect(fan.pm10).toBe(30);
73
+ expect(fan.aqPercent).toBe(80);
74
+ expect(fan.airQualityLevel).toBe('Good');
75
+ });
76
+ it('should handle numeric air quality levels', () => {
77
+ fan.details.air_quality = 2;
78
+ expect(fan.airQualityLevel).toBe(2);
79
+ });
80
+ it('should maintain backward compatibility with airQuality getter', () => {
81
+ fan.details.air_quality = 'Excellent';
82
+ expect(fan.airQuality).toBe('Excellent');
83
+ expect(fan.airQuality).toBe(fan.airQualityLevel);
84
+ });
85
+ it('should handle extreme air quality values', () => {
86
+ fan.details.air_quality_value = 999;
87
+ fan.details.pm1 = 500;
88
+ fan.details.pm10 = 1000;
89
+ fan.details.aq_percent = 100;
90
+ expect(fan.airQualityValue).toBe(999);
91
+ expect(fan.pm1).toBe(500);
92
+ expect(fan.pm10).toBe(1000);
93
+ expect(fan.aqPercent).toBe(100);
94
+ });
95
+ it('should handle zero air quality values', () => {
96
+ fan.details.air_quality_value = 0;
97
+ fan.details.pm1 = 0;
98
+ fan.details.pm10 = 0;
99
+ fan.details.aq_percent = 0;
100
+ expect(fan.airQualityValue).toBe(0);
101
+ expect(fan.pm1).toBe(0);
102
+ expect(fan.pm10).toBe(0);
103
+ expect(fan.aqPercent).toBe(0);
104
+ });
105
+ });
106
+ describe('Filter Life Handling', () => {
107
+ it('should return 0 when filter life is not available', () => {
108
+ expect(fan.filterLife).toBe(0);
109
+ });
110
+ it('should handle number format filter life (VeSyncAirBypass)', () => {
111
+ fan.details.filter_life = 85;
112
+ expect(fan.filterLife).toBe(85);
113
+ });
114
+ it('should handle object format filter life (VeSyncAir131)', () => {
115
+ fan.details.filter_life = { percent: 72 };
116
+ expect(fan.filterLife).toBe(72);
117
+ });
118
+ it('should handle object format with missing percent property', () => {
119
+ fan.details.filter_life = { other: 'value' };
120
+ expect(fan.filterLife).toBe(0);
121
+ });
122
+ it('should handle null filter life object', () => {
123
+ fan.details.filter_life = null;
124
+ expect(fan.filterLife).toBe(0);
125
+ });
126
+ it('should handle undefined filter life', () => {
127
+ fan.details.filter_life = undefined;
128
+ expect(fan.filterLife).toBe(0);
129
+ });
130
+ it('should handle zero filter life values', () => {
131
+ fan.details.filter_life = 0;
132
+ expect(fan.filterLife).toBe(0);
133
+ fan.details.filter_life = { percent: 0 };
134
+ expect(fan.filterLife).toBe(0);
135
+ });
136
+ it('should handle 100% filter life', () => {
137
+ fan.details.filter_life = 100;
138
+ expect(fan.filterLife).toBe(100);
139
+ fan.details.filter_life = { percent: 100 };
140
+ expect(fan.filterLife).toBe(100);
141
+ });
142
+ it('should handle invalid filter life formats gracefully', () => {
143
+ fan.details.filter_life = 'invalid';
144
+ expect(fan.filterLife).toBe(0);
145
+ fan.details.filter_life = [];
146
+ expect(fan.filterLife).toBe(0);
147
+ });
148
+ });
149
+ describe('Feature Detection', () => {
150
+ it('should detect air quality feature for supported devices', () => {
151
+ expect(fan.hasFeature('air_quality')).toBe(true);
152
+ });
153
+ it('should detect supported features based on device type', () => {
154
+ expect(fan.hasFeature('display')).toBe(true);
155
+ expect(fan.hasFeature('child_lock')).toBe(true);
156
+ expect(fan.hasFeature('night_light')).toBe(true);
157
+ expect(fan.hasFeature('timer')).toBe(true);
158
+ expect(fan.hasFeature('fan_speed')).toBe(true);
159
+ expect(fan.hasFeature('auto_mode')).toBe(true);
160
+ expect(fan.hasFeature('sleep_mode')).toBe(true);
161
+ });
162
+ it('should return false for unsupported features', () => {
163
+ expect(fan.hasFeature('unsupported_feature')).toBe(false);
164
+ });
165
+ it('should handle devices without configuration', () => {
166
+ const unknownDevice = new TestVeSyncFan({
167
+ deviceName: 'Unknown Device',
168
+ deviceType: 'UnknownType',
169
+ cid: 'test-cid',
170
+ uuid: 'test-uuid'
171
+ }, mockManager);
172
+ expect(unknownDevice.hasFeature('air_quality')).toBe(false);
173
+ });
174
+ });
175
+ describe('Feature Support in Current Mode', () => {
176
+ it('should support air quality in all modes', () => {
177
+ fan.details.mode = 'auto';
178
+ expect(fan.isFeatureSupportedInCurrentMode('air_quality')).toBe(true);
179
+ fan.details.mode = 'manual';
180
+ expect(fan.isFeatureSupportedInCurrentMode('air_quality')).toBe(true);
181
+ fan.details.mode = 'sleep';
182
+ expect(fan.isFeatureSupportedInCurrentMode('air_quality')).toBe(true);
183
+ });
184
+ it('should disable display and child lock in sleep mode', () => {
185
+ fan.details.mode = 'sleep';
186
+ expect(fan.isFeatureSupportedInCurrentMode('display')).toBe(false);
187
+ expect(fan.isFeatureSupportedInCurrentMode('child_lock')).toBe(false);
188
+ });
189
+ it('should support display and child lock in other modes', () => {
190
+ fan.details.mode = 'auto';
191
+ expect(fan.isFeatureSupportedInCurrentMode('display')).toBe(true);
192
+ expect(fan.isFeatureSupportedInCurrentMode('child_lock')).toBe(true);
193
+ fan.details.mode = 'manual';
194
+ expect(fan.isFeatureSupportedInCurrentMode('display')).toBe(true);
195
+ expect(fan.isFeatureSupportedInCurrentMode('child_lock')).toBe(true);
196
+ });
197
+ it('should return false for unsupported features regardless of mode', () => {
198
+ fan.details.mode = 'auto';
199
+ expect(fan.isFeatureSupportedInCurrentMode('unsupported')).toBe(false);
200
+ });
201
+ });
202
+ describe('Device Capabilities', () => {
203
+ it('should return correct maximum fan speed', () => {
204
+ expect(fan.getMaxFanSpeed()).toBe(4); // Core300S has 4 speeds
205
+ });
206
+ it('should handle devices without speed levels configured', () => {
207
+ const deviceWithoutLevels = new TestVeSyncFan({
208
+ deviceName: 'Basic Device',
209
+ deviceType: 'UnknownType',
210
+ cid: 'test-cid',
211
+ uuid: 'test-uuid'
212
+ }, mockManager);
213
+ expect(deviceWithoutLevels.getMaxFanSpeed()).toBe(3); // Default fallback
214
+ });
215
+ it('should return current mode and speed', () => {
216
+ fan.details.mode = 'auto';
217
+ fan.details.speed = 3;
218
+ expect(fan.mode).toBe('auto');
219
+ expect(fan.speed).toBe(3);
220
+ });
221
+ it('should handle missing mode and speed', () => {
222
+ expect(fan.mode).toBe('');
223
+ expect(fan.speed).toBe(0);
224
+ });
225
+ });
226
+ describe('Edge Cases and Error Handling', () => {
227
+ it('should handle missing details object', () => {
228
+ const minimalFan = new TestVeSyncFan({
229
+ deviceName: 'Minimal Device',
230
+ deviceType: 'Core300S',
231
+ cid: 'test-cid',
232
+ uuid: 'test-uuid'
233
+ }, mockManager);
234
+ // Details should be initialized as empty object
235
+ expect(minimalFan.airQualityValue).toBe(0);
236
+ expect(minimalFan.filterLife).toBe(0);
237
+ expect(minimalFan.mode).toBe('');
238
+ expect(minimalFan.speed).toBe(0);
239
+ });
240
+ it('should handle null and undefined values gracefully', () => {
241
+ fan.details.air_quality_value = null;
242
+ fan.details.filter_life = null;
243
+ fan.details.mode = null;
244
+ fan.details.speed = null;
245
+ expect(fan.airQualityValue).toBe(0);
246
+ expect(fan.filterLife).toBe(0);
247
+ expect(fan.mode).toBe('');
248
+ expect(fan.speed).toBe(0);
249
+ });
250
+ it('should handle invalid data types', () => {
251
+ fan.details.air_quality_value = 'invalid';
252
+ fan.details.pm1 = 'invalid';
253
+ fan.details.pm10 = 'invalid';
254
+ fan.details.aq_percent = 'invalid';
255
+ fan.details.speed = 'invalid';
256
+ expect(fan.airQualityValue).toBe(0);
257
+ expect(fan.pm1).toBe(0);
258
+ expect(fan.pm10).toBe(0);
259
+ expect(fan.aqPercent).toBe(0);
260
+ expect(fan.speed).toBe(0);
261
+ });
262
+ });
263
+ });
@@ -56,10 +56,11 @@ class VeSyncAir131 extends vesyncFan_1.VeSyncFan {
56
56
  this.details = {
57
57
  mode: data.mode || 'manual', // Default to manual if not specified
58
58
  speed: data.level || 0,
59
- filterLife: data.filterLife || 0,
60
- screenStatus: data.screenStatus || 'off',
61
- childLock: data.childLock || false,
62
- airQuality: data.airQuality || 'unknown',
59
+ filter_life: data.filterLife || data.filter_life || 0,
60
+ screen_status: data.screenStatus || 'off',
61
+ child_lock: data.childLock || false,
62
+ air_quality: data.airQuality || 'unknown',
63
+ air_quality_value: data.air_quality_value || 0,
63
64
  active_time: data.activeTime || 0
64
65
  };
65
66
  // Store last known speed when device is on and has a valid speed
@@ -56,11 +56,19 @@ class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
56
56
  this.details = {
57
57
  mode: result.mode || '',
58
58
  speed: result.level || 0,
59
- filterLife: result.filter_life || 0,
60
- screenStatus: result.display ? 'on' : 'off',
61
- childLock: result.child_lock || false,
62
- airQuality: result.air_quality || 0
59
+ filter_life: result.filter_life || 0,
60
+ screen_status: result.display ? 'on' : 'off',
61
+ child_lock: result.child_lock || false,
62
+ air_quality: result.air_quality || 0,
63
+ air_quality_value: result.air_quality_value || 0
63
64
  };
65
+ // Capture additional air quality properties if available
66
+ if ('PM1' in result)
67
+ this.details.pm1 = result.PM1;
68
+ if ('PM10' in result)
69
+ this.details.pm10 = result.PM10;
70
+ if ('AQPercent' in result)
71
+ this.details.aq_percent = result.AQPercent;
64
72
  // Don't overwrite config as it contains features array
65
73
  if (result.configuration) {
66
74
  this.details.configuration = result.configuration;
@@ -2,21 +2,8 @@
2
2
  * Helper functions for VeSync API
3
3
  */
4
4
  import { VeSync } from './vesync';
5
- export declare const REGIONAL_ENDPOINTS: {
6
- readonly US: "https://smartapi.vesync.com";
7
- readonly EU: "https://smartapi.vesync.eu";
8
- readonly ASIA: "https://smartapi.vesync.com.cn";
9
- };
10
5
  export declare function getApiBaseUrl(): string;
11
6
  export declare function setApiBaseUrl(url: string): void;
12
- /**
13
- * Get regional API endpoint based on timezone
14
- */
15
- export declare function getRegionalEndpoint(timeZone: string): string;
16
- /**
17
- * Set API endpoint based on timezone if not explicitly set
18
- */
19
- export declare function setRegionalEndpoint(timeZone: string): void;
20
7
  export declare const API_RATE_LIMIT = 30;
21
8
  export declare const API_TIMEOUT = 15000;
22
9
  export declare const APP_VERSION = "2.8.6";
@@ -6,174 +6,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  return (mod && mod.__esModule) ? mod : { "default": mod };
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.Helpers = exports.BYPASS_HEADER_UA = exports.MOBILE_ID = exports.DEFAULT_REGION = exports.DEFAULT_TZ = exports.USER_TYPE = exports.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = exports.REGIONAL_ENDPOINTS = void 0;
9
+ exports.Helpers = exports.BYPASS_HEADER_UA = exports.MOBILE_ID = exports.DEFAULT_REGION = exports.DEFAULT_TZ = exports.USER_TYPE = exports.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = void 0;
10
10
  exports.getApiBaseUrl = getApiBaseUrl;
11
11
  exports.setApiBaseUrl = setApiBaseUrl;
12
- exports.getRegionalEndpoint = getRegionalEndpoint;
13
- exports.setRegionalEndpoint = setRegionalEndpoint;
14
12
  const axios_1 = __importDefault(require("axios"));
15
13
  const crypto_1 = __importDefault(require("crypto"));
16
14
  const logger_1 = require("./logger");
17
- // API configuration
15
+ // API configuration - Always use US endpoint
18
16
  let _apiBaseUrl = 'https://smartapi.vesync.com';
19
- // Regional API endpoints
20
- exports.REGIONAL_ENDPOINTS = {
21
- US: 'https://smartapi.vesync.com',
22
- EU: 'https://smartapi.vesync.eu',
23
- ASIA: 'https://smartapi.vesync.com.cn'
24
- };
25
- // Timezone to region mapping for API endpoint detection
26
- const TIMEZONE_REGION_MAP = {
27
- // European timezones - Western Europe
28
- 'Europe/Berlin': 'EU',
29
- 'Europe/London': 'EU',
30
- 'Europe/Paris': 'EU',
31
- 'Europe/Rome': 'EU',
32
- 'Europe/Madrid': 'EU',
33
- 'Europe/Amsterdam': 'EU',
34
- 'Europe/Vienna': 'EU',
35
- 'Europe/Brussels': 'EU',
36
- 'Europe/Copenhagen': 'EU',
37
- 'Europe/Dublin': 'EU',
38
- 'Europe/Helsinki': 'EU',
39
- 'Europe/Lisbon': 'EU',
40
- 'Europe/Luxembourg': 'EU',
41
- 'Europe/Oslo': 'EU',
42
- 'Europe/Prague': 'EU',
43
- 'Europe/Stockholm': 'EU',
44
- 'Europe/Warsaw': 'EU',
45
- 'Europe/Zurich': 'EU',
46
- // European timezones - Eastern Europe
47
- 'Europe/Athens': 'EU',
48
- 'Europe/Budapest': 'EU',
49
- 'Europe/Bucharest': 'EU',
50
- 'Europe/Sofia': 'EU',
51
- 'Europe/Kiev': 'EU',
52
- 'Europe/Riga': 'EU',
53
- 'Europe/Vilnius': 'EU',
54
- 'Europe/Tallinn': 'EU',
55
- 'Europe/Zagreb': 'EU',
56
- 'Europe/Ljubljana': 'EU',
57
- 'Europe/Bratislava': 'EU',
58
- 'Europe/Belgrade': 'EU',
59
- 'Europe/Sarajevo': 'EU',
60
- 'Europe/Podgorica': 'EU',
61
- 'Europe/Skopje': 'EU',
62
- 'Europe/Tirane': 'EU',
63
- // European timezones - Nordic/Baltic
64
- 'Europe/Reykjavik': 'EU',
65
- 'Europe/Moscow': 'EU',
66
- 'Europe/Kaliningrad': 'EU',
67
- 'Europe/Minsk': 'EU',
68
- // European timezones - Other
69
- 'Europe/Malta': 'EU',
70
- 'Europe/Monaco': 'EU',
71
- 'Europe/Vatican': 'EU',
72
- 'Europe/San_Marino': 'EU',
73
- 'Europe/Andorra': 'EU',
74
- 'Europe/Gibraltar': 'EU',
75
- 'Europe/Isle_of_Man': 'EU',
76
- 'Europe/Jersey': 'EU',
77
- 'Europe/Guernsey': 'EU',
78
- // Asian timezones - East Asia
79
- 'Asia/Shanghai': 'ASIA',
80
- 'Asia/Beijing': 'ASIA',
81
- 'Asia/Hong_Kong': 'ASIA',
82
- 'Asia/Tokyo': 'ASIA',
83
- 'Asia/Seoul': 'ASIA',
84
- 'Asia/Singapore': 'ASIA',
85
- 'Asia/Taipei': 'ASIA',
86
- 'Asia/Macau': 'ASIA',
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
- // Asian timezones - South Asia
98
- 'Asia/Mumbai': 'ASIA',
99
- 'Asia/Delhi': 'ASIA',
100
- 'Asia/Kolkata': 'ASIA',
101
- 'Asia/Dhaka': 'ASIA',
102
- 'Asia/Karachi': 'ASIA',
103
- 'Asia/Colombo': 'ASIA',
104
- 'Asia/Kathmandu': 'ASIA',
105
- 'Asia/Thimphu': 'ASIA',
106
- // Asian timezones - Central Asia
107
- 'Asia/Almaty': 'ASIA',
108
- 'Asia/Bishkek': 'ASIA',
109
- 'Asia/Dushanbe': 'ASIA',
110
- 'Asia/Tashkent': 'ASIA',
111
- 'Asia/Ashgabat': 'ASIA',
112
- // Asian timezones - West Asia / Middle East
113
- 'Asia/Dubai': 'ASIA',
114
- 'Asia/Riyadh': 'ASIA',
115
- 'Asia/Tehran': 'ASIA',
116
- 'Asia/Baghdad': 'ASIA',
117
- 'Asia/Kuwait': 'ASIA',
118
- 'Asia/Qatar': 'ASIA',
119
- 'Asia/Bahrain': 'ASIA',
120
- 'Asia/Muscat': 'ASIA',
121
- 'Asia/Baku': 'ASIA',
122
- 'Asia/Yerevan': 'ASIA',
123
- 'Asia/Tbilisi': 'ASIA',
124
- // Oceania (using ASIA endpoint for now)
125
- 'Australia/Sydney': 'ASIA',
126
- 'Australia/Melbourne': 'ASIA',
127
- 'Australia/Brisbane': 'ASIA',
128
- 'Australia/Perth': 'ASIA',
129
- 'Australia/Adelaide': 'ASIA',
130
- 'Australia/Hobart': 'ASIA',
131
- 'Australia/Darwin': 'ASIA',
132
- 'Australia/Canberra': 'ASIA',
133
- 'Pacific/Auckland': 'ASIA',
134
- 'Pacific/Wellington': 'ASIA',
135
- 'Pacific/Fiji': 'ASIA',
136
- 'Pacific/Honolulu': 'US',
137
- // North America (Canada)
138
- 'America/Toronto': 'US',
139
- 'America/Montreal': 'US',
140
- 'America/Vancouver': 'US',
141
- 'America/Calgary': 'US',
142
- 'America/Edmonton': 'US',
143
- 'America/Winnipeg': 'US',
144
- 'America/Halifax': 'US',
145
- 'America/St_Johns': 'US',
146
- // North America (Mexico)
147
- 'America/Mexico_City': 'US',
148
- 'America/Tijuana': 'US',
149
- 'America/Cancun': 'US',
150
- 'America/Merida': 'US',
151
- 'America/Monterrey': 'US'
152
- // US and other timezones default to US endpoint
153
- };
154
17
  function getApiBaseUrl() {
155
18
  return _apiBaseUrl;
156
19
  }
157
20
  function setApiBaseUrl(url) {
158
21
  _apiBaseUrl = url;
159
22
  }
160
- /**
161
- * Get regional API endpoint based on timezone
162
- */
163
- function getRegionalEndpoint(timeZone) {
164
- const region = TIMEZONE_REGION_MAP[timeZone] || 'US';
165
- return exports.REGIONAL_ENDPOINTS[region];
166
- }
167
- /**
168
- * Set API endpoint based on timezone if not explicitly set
169
- */
170
- function setRegionalEndpoint(timeZone) {
171
- const regionalUrl = getRegionalEndpoint(timeZone);
172
- if (_apiBaseUrl === exports.REGIONAL_ENDPOINTS.US && regionalUrl !== exports.REGIONAL_ENDPOINTS.US) {
173
- logger_1.logger.debug(`Setting regional API endpoint for ${timeZone}: ${regionalUrl}`);
174
- setApiBaseUrl(regionalUrl);
175
- }
176
- }
177
23
  exports.API_RATE_LIMIT = 30;
178
24
  exports.API_TIMEOUT = 15000;
179
25
  exports.APP_VERSION = '2.8.6';
@@ -320,8 +166,8 @@ class Helpers {
320
166
  try {
321
167
  // Ensure API base URL is properly set
322
168
  if (!_apiBaseUrl || _apiBaseUrl === 'undefined') {
323
- logger_1.logger.error('API base URL is not properly configured. Setting regional endpoint...');
324
- setRegionalEndpoint(manager.timeZone);
169
+ logger_1.logger.error('API base URL is not properly configured. Setting to default US endpoint...');
170
+ setApiBaseUrl('https://smartapi.vesync.com');
325
171
  }
326
172
  const url = _apiBaseUrl + endpoint;
327
173
  logger_1.logger.debug(`Making API call to: ${url}`);
@@ -43,7 +43,7 @@ export declare class VeSync {
43
43
  * Initialize VeSync Manager
44
44
  * @param username - VeSync account username
45
45
  * @param password - VeSync account password
46
- * @param timeZone - Optional timezone (defaults to America/New_York)
46
+ * @param timeZone - Optional timezone for device operations (defaults to America/New_York)
47
47
  * @param debug - Optional debug mode flag
48
48
  * @param redact - Optional redact mode flag
49
49
  * @param apiUrl - Optional API base URL override
@@ -124,7 +124,7 @@ class VeSync {
124
124
  * Initialize VeSync Manager
125
125
  * @param username - VeSync account username
126
126
  * @param password - VeSync account password
127
- * @param timeZone - Optional timezone (defaults to America/New_York)
127
+ * @param timeZone - Optional timezone for device operations (defaults to America/New_York)
128
128
  * @param debug - Optional debug mode flag
129
129
  * @param redact - Optional redact mode flag
130
130
  * @param apiUrl - Optional API base URL override
@@ -173,13 +173,13 @@ class VeSync {
173
173
  this.timeZone = helpers_1.DEFAULT_TZ;
174
174
  logger_1.logger.debug('Time zone is not a string');
175
175
  }
176
- // Set custom API URL if provided, otherwise use regional endpoint
176
+ // Set custom API URL if provided, otherwise use default US endpoint
177
177
  if (apiUrl) {
178
178
  (0, helpers_1.setApiBaseUrl)(apiUrl);
179
179
  }
180
180
  else {
181
- // Automatically set regional endpoint based on timezone
182
- (0, helpers_1.setRegionalEndpoint)(this.timeZone);
181
+ // Always use US endpoint
182
+ (0, helpers_1.setApiBaseUrl)('https://smartapi.vesync.com');
183
183
  }
184
184
  // Set custom logger if provided
185
185
  if (customLogger) {
@@ -428,7 +428,7 @@ class VeSync {
428
428
  switch (response.code) {
429
429
  case -11012022:
430
430
  logger_1.logger.error('App version too low error. Current version:', body.appVersion);
431
- logger_1.logger.error('This typically indicates a regional API compatibility issue.');
431
+ logger_1.logger.error('This typically indicates an API version compatibility issue.');
432
432
  break;
433
433
  case -11003:
434
434
  logger_1.logger.error('Authentication failed - check credentials');
@@ -18,10 +18,16 @@ export declare abstract class VeSyncFan extends VeSyncBaseDevice {
18
18
  protected details: {
19
19
  mode?: string;
20
20
  speed?: number;
21
- filter_life?: number;
21
+ filter_life?: number | {
22
+ percent: number;
23
+ };
22
24
  screen_status?: 'on' | 'off';
23
25
  child_lock?: boolean;
24
- air_quality?: string;
26
+ air_quality?: string | number;
27
+ air_quality_value?: number;
28
+ pm1?: number;
29
+ pm10?: number;
30
+ aq_percent?: number;
25
31
  humidity?: number;
26
32
  mist_level?: number;
27
33
  warm_level?: number;
@@ -73,9 +79,29 @@ export declare abstract class VeSyncFan extends VeSyncBaseDevice {
73
79
  */
74
80
  get childLock(): boolean;
75
81
  /**
76
- * Get air quality (if supported)
82
+ * Get air quality level (if supported)
77
83
  */
78
- get airQuality(): string;
84
+ get airQualityLevel(): string | number;
85
+ /**
86
+ * Get air quality (alias for airQualityLevel for backward compatibility)
87
+ */
88
+ get airQuality(): string | number;
89
+ /**
90
+ * Get air quality value - PM2.5 in μg/m³ (if supported)
91
+ */
92
+ get airQualityValue(): number;
93
+ /**
94
+ * Get PM1 value (if supported)
95
+ */
96
+ get pm1(): number;
97
+ /**
98
+ * Get PM10 value (if supported)
99
+ */
100
+ get pm10(): number;
101
+ /**
102
+ * Get air quality percentage (if supported)
103
+ */
104
+ get aqPercent(): number;
79
105
  /**
80
106
  * Get humidity level (if supported)
81
107
  */