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.
- package/README.md +1 -36
- package/dist/__tests__/fans/vesyncAir131.test.d.ts +4 -0
- package/dist/__tests__/fans/vesyncAir131.test.js +365 -0
- package/dist/__tests__/fans/vesyncAirBaseV2.test.d.ts +4 -0
- package/dist/__tests__/fans/vesyncAirBaseV2.test.js +283 -0
- package/dist/__tests__/fans/vesyncAirBypass.test.d.ts +4 -0
- package/dist/__tests__/fans/vesyncAirBypass.test.js +257 -0
- package/dist/__tests__/vesyncFan.test.d.ts +4 -0
- package/dist/__tests__/vesyncFan.test.js +263 -0
- package/dist/lib/fans/air131.js +5 -4
- package/dist/lib/fans/airBypass.js +12 -4
- package/dist/lib/helpers.d.ts +0 -13
- package/dist/lib/helpers.js +4 -158
- package/dist/lib/vesync.d.ts +1 -1
- package/dist/lib/vesync.js +5 -5
- package/dist/lib/vesyncFan.d.ts +30 -4
- package/dist/lib/vesyncFan.js +70 -5
- package/package.json +1 -1
- package/src/lib/fans/air131.ts +5 -4
- package/src/lib/fans/airBypass.ts +10 -4
- package/src/lib/helpers.ts +3 -169
- package/src/lib/vesync.ts +6 -6
- package/src/lib/vesyncFan.ts +79 -7
package/README.md
CHANGED
|
@@ -201,42 +201,7 @@ The library supports configuration through environment variables:
|
|
|
201
201
|
```env
|
|
202
202
|
VESYNC_USERNAME=your_email@example.com
|
|
203
203
|
VESYNC_PASSWORD=your_password
|
|
204
|
-
VESYNC_API_URL=https://smartapi.vesync.com # Optional,
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Regional API Support
|
|
208
|
-
|
|
209
|
-
The library automatically detects the correct regional API endpoint based on your timezone:
|
|
210
|
-
|
|
211
|
-
- **Europe**: `https://smartapi.vesync.eu` (for European timezones like `Europe/Berlin`, `Europe/London`)
|
|
212
|
-
- **Asia**: `https://smartapi.vesync.com.cn` (for Asian timezones like `Asia/Shanghai`, `Asia/Tokyo`)
|
|
213
|
-
- **US/Others**: `https://smartapi.vesync.com` (default for all other timezones)
|
|
214
|
-
|
|
215
|
-
**For German users experiencing authentication issues:**
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
// This will automatically use the EU API endpoint
|
|
219
|
-
const manager = new VeSync(
|
|
220
|
-
'your_email@example.com',
|
|
221
|
-
'your_password',
|
|
222
|
-
'Europe/Berlin' // This triggers EU endpoint selection
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
await manager.login();
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**Manual API endpoint override:**
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
// Force a specific regional endpoint
|
|
232
|
-
const manager = new VeSync(
|
|
233
|
-
'your_email@example.com',
|
|
234
|
-
'your_password',
|
|
235
|
-
'Europe/Berlin',
|
|
236
|
-
false, // debug
|
|
237
|
-
true, // redact
|
|
238
|
-
'https://smartapi.vesync.eu' // Force EU endpoint
|
|
239
|
-
);
|
|
204
|
+
VESYNC_API_URL=https://smartapi.vesync.com # Optional, defaults to standard API URL
|
|
240
205
|
```
|
|
241
206
|
|
|
242
207
|
## Testing
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for VeSyncAir131 air quality data parsing and filter life handling
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const air131_1 = require("../../lib/fans/air131");
|
|
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
|
+
describe('VeSyncAir131 Air Quality Features', () => {
|
|
18
|
+
let device;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
const deviceDetails = {
|
|
21
|
+
deviceName: 'LV-PUR131S Test',
|
|
22
|
+
deviceType: 'LV-PUR131S',
|
|
23
|
+
cid: 'test-cid',
|
|
24
|
+
uuid: 'test-uuid',
|
|
25
|
+
deviceStatus: 'on',
|
|
26
|
+
deviceRegion: 'US',
|
|
27
|
+
configModule: 'VeSyncAir131',
|
|
28
|
+
macId: '00:11:22:33:44:55',
|
|
29
|
+
deviceCategory: 'fan',
|
|
30
|
+
connectionStatus: 'online'
|
|
31
|
+
};
|
|
32
|
+
device = new air131_1.VeSyncAir131(deviceDetails, mockManager);
|
|
33
|
+
});
|
|
34
|
+
describe('Air Quality Data Parsing', () => {
|
|
35
|
+
it('should parse air quality data with PM2.5 values', () => {
|
|
36
|
+
const mockResponse = {
|
|
37
|
+
result: {
|
|
38
|
+
code: 0,
|
|
39
|
+
result: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
mode: 'auto',
|
|
42
|
+
level: 2,
|
|
43
|
+
speed: 2,
|
|
44
|
+
display: true,
|
|
45
|
+
child_lock: false,
|
|
46
|
+
night_light: 'off',
|
|
47
|
+
air_quality: 2,
|
|
48
|
+
air_quality_value: 28,
|
|
49
|
+
filter_life: {
|
|
50
|
+
percent: 68,
|
|
51
|
+
replace_indicator: false
|
|
52
|
+
},
|
|
53
|
+
timer_remain: 0
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
device.details = mockResponse.result.result;
|
|
58
|
+
expect(device.airQualityLevel).toBe(2);
|
|
59
|
+
expect(device.airQualityValue).toBe(28);
|
|
60
|
+
expect(device.filterLife).toBe(68);
|
|
61
|
+
});
|
|
62
|
+
it('should handle air quality levels 1-5 mapping', () => {
|
|
63
|
+
const airQualityMappings = [
|
|
64
|
+
{ level: 1, pm25: 8, description: 'Excellent' },
|
|
65
|
+
{ level: 2, pm25: 22, description: 'Good' },
|
|
66
|
+
{ level: 3, pm25: 38, description: 'Fair' },
|
|
67
|
+
{ level: 4, pm25: 75, description: 'Poor' },
|
|
68
|
+
{ level: 5, pm25: 150, description: 'Very Poor' }
|
|
69
|
+
];
|
|
70
|
+
airQualityMappings.forEach(mapping => {
|
|
71
|
+
device.details = {
|
|
72
|
+
air_quality: mapping.level,
|
|
73
|
+
air_quality_value: mapping.pm25,
|
|
74
|
+
filter_life: { percent: 50 }
|
|
75
|
+
};
|
|
76
|
+
expect(device.airQualityLevel).toBe(mapping.level);
|
|
77
|
+
expect(device.airQualityValue).toBe(mapping.pm25);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
it('should handle missing air quality data', () => {
|
|
81
|
+
device.details = {
|
|
82
|
+
enabled: true,
|
|
83
|
+
mode: 'manual',
|
|
84
|
+
level: 3,
|
|
85
|
+
speed: 3,
|
|
86
|
+
filter_life: { percent: 45 }
|
|
87
|
+
// air_quality and air_quality_value missing
|
|
88
|
+
};
|
|
89
|
+
expect(device.airQualityLevel).toBe('');
|
|
90
|
+
expect(device.airQualityValue).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
it('should handle zero PM2.5 readings (perfect air)', () => {
|
|
93
|
+
device.details = {
|
|
94
|
+
air_quality: 1,
|
|
95
|
+
air_quality_value: 0,
|
|
96
|
+
filter_life: { percent: 85 }
|
|
97
|
+
};
|
|
98
|
+
expect(device.airQualityLevel).toBe(1);
|
|
99
|
+
expect(device.airQualityValue).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
it('should handle hazardous air quality levels', () => {
|
|
102
|
+
device.details = {
|
|
103
|
+
air_quality: 5,
|
|
104
|
+
air_quality_value: 500,
|
|
105
|
+
filter_life: { percent: 25 }
|
|
106
|
+
};
|
|
107
|
+
expect(device.airQualityLevel).toBe(5);
|
|
108
|
+
expect(device.airQualityValue).toBe(500);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('Filter Life Object Format', () => {
|
|
112
|
+
it('should parse filter life object with percent', () => {
|
|
113
|
+
device.details = {
|
|
114
|
+
filter_life: {
|
|
115
|
+
percent: 72,
|
|
116
|
+
replace_indicator: false
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
expect(device.filterLife).toBe(72);
|
|
120
|
+
});
|
|
121
|
+
it('should handle filter replacement indicator', () => {
|
|
122
|
+
device.details = {
|
|
123
|
+
filter_life: {
|
|
124
|
+
percent: 8,
|
|
125
|
+
replace_indicator: true
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
expect(device.filterLife).toBe(8);
|
|
129
|
+
});
|
|
130
|
+
it('should handle new filter (100%)', () => {
|
|
131
|
+
device.details = {
|
|
132
|
+
filter_life: {
|
|
133
|
+
percent: 100,
|
|
134
|
+
replace_indicator: false
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
expect(device.filterLife).toBe(100);
|
|
138
|
+
});
|
|
139
|
+
it('should handle filter needs replacement (0%)', () => {
|
|
140
|
+
device.details = {
|
|
141
|
+
filter_life: {
|
|
142
|
+
percent: 0,
|
|
143
|
+
replace_indicator: true
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
expect(device.filterLife).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
it('should handle missing percent in filter object', () => {
|
|
149
|
+
device.details = {
|
|
150
|
+
filter_life: {
|
|
151
|
+
replace_indicator: true
|
|
152
|
+
// percent missing
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
expect(device.filterLife).toBe(0);
|
|
156
|
+
});
|
|
157
|
+
it('should handle null filter life object', () => {
|
|
158
|
+
device.details = {
|
|
159
|
+
filter_life: null
|
|
160
|
+
};
|
|
161
|
+
expect(device.filterLife).toBe(0);
|
|
162
|
+
});
|
|
163
|
+
it('should handle empty filter life object', () => {
|
|
164
|
+
device.details = {
|
|
165
|
+
filter_life: {}
|
|
166
|
+
};
|
|
167
|
+
expect(device.filterLife).toBe(0);
|
|
168
|
+
});
|
|
169
|
+
it('should handle malformed filter life object', () => {
|
|
170
|
+
device.details = {
|
|
171
|
+
filter_life: {
|
|
172
|
+
percent: 'not_a_number',
|
|
173
|
+
replace_indicator: 'not_a_boolean'
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
expect(device.filterLife).toBe(0);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
describe('Device Configuration and Features', () => {
|
|
180
|
+
it('should support air quality features for LV-PUR131S', () => {
|
|
181
|
+
expect(device.hasFeature('air_quality')).toBe(true);
|
|
182
|
+
expect(device.hasFeature('display')).toBe(true);
|
|
183
|
+
expect(device.hasFeature('child_lock')).toBe(true);
|
|
184
|
+
expect(device.hasFeature('night_light')).toBe(true);
|
|
185
|
+
expect(device.hasFeature('timer')).toBe(true);
|
|
186
|
+
expect(device.hasFeature('fan_speed')).toBe(true);
|
|
187
|
+
expect(device.hasFeature('auto_mode')).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
it('should return correct maximum speed for LV-PUR131S', () => {
|
|
190
|
+
expect(device.getMaxFanSpeed()).toBe(3); // LV series has 3 speeds
|
|
191
|
+
});
|
|
192
|
+
it('should handle LV-RH131S without air quality', () => {
|
|
193
|
+
const rh131s = new air131_1.VeSyncAir131({
|
|
194
|
+
deviceName: 'LV-RH131S Test',
|
|
195
|
+
deviceType: 'LV-RH131S',
|
|
196
|
+
cid: 'test-cid',
|
|
197
|
+
uuid: 'test-uuid'
|
|
198
|
+
}, mockManager);
|
|
199
|
+
expect(rh131s.hasFeature('air_quality')).toBe(false);
|
|
200
|
+
expect(rh131s.hasFeature('display')).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe('Mode-specific Behavior', () => {
|
|
204
|
+
it('should support air quality in auto mode', () => {
|
|
205
|
+
device.details.mode = 'auto';
|
|
206
|
+
expect(device.isFeatureSupportedInCurrentMode('air_quality')).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
it('should support air quality in manual mode', () => {
|
|
209
|
+
device.details.mode = 'manual';
|
|
210
|
+
expect(device.isFeatureSupportedInCurrentMode('air_quality')).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
it('should restrict fan speed control to manual mode', () => {
|
|
213
|
+
device.details.mode = 'manual';
|
|
214
|
+
expect(device.isFeatureSupportedInCurrentMode('fan_speed')).toBe(true);
|
|
215
|
+
device.details.mode = 'auto';
|
|
216
|
+
expect(device.isFeatureSupportedInCurrentMode('fan_speed')).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
it('should disable display and child lock in sleep mode', () => {
|
|
219
|
+
device.details.mode = 'sleep';
|
|
220
|
+
expect(device.isFeatureSupportedInCurrentMode('display')).toBe(false);
|
|
221
|
+
expect(device.isFeatureSupportedInCurrentMode('child_lock')).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
describe('State Tracking and Updates', () => {
|
|
225
|
+
it('should track complete device state', () => {
|
|
226
|
+
device.details = {
|
|
227
|
+
enabled: true,
|
|
228
|
+
mode: 'auto',
|
|
229
|
+
level: 2,
|
|
230
|
+
speed: 2,
|
|
231
|
+
display: true,
|
|
232
|
+
child_lock: false,
|
|
233
|
+
night_light: 'dim',
|
|
234
|
+
air_quality: 2,
|
|
235
|
+
air_quality_value: 32,
|
|
236
|
+
filter_life: {
|
|
237
|
+
percent: 55,
|
|
238
|
+
replace_indicator: false
|
|
239
|
+
},
|
|
240
|
+
timer_remain: 1800
|
|
241
|
+
};
|
|
242
|
+
expect(device.deviceStatus).toBe('on');
|
|
243
|
+
expect(device.mode).toBe('auto');
|
|
244
|
+
expect(device.speed).toBe(2);
|
|
245
|
+
expect(device.screenStatus).toBe('on'); // display: true maps to 'on'
|
|
246
|
+
expect(device.childLock).toBe(false);
|
|
247
|
+
expect(device.airQualityLevel).toBe(2);
|
|
248
|
+
expect(device.airQualityValue).toBe(32);
|
|
249
|
+
expect(device.filterLife).toBe(55);
|
|
250
|
+
});
|
|
251
|
+
it('should handle boolean display values correctly', () => {
|
|
252
|
+
device.details = { display: true };
|
|
253
|
+
expect(device.screenStatus).toBe('on');
|
|
254
|
+
device.details = { display: false };
|
|
255
|
+
expect(device.screenStatus).toBe('off');
|
|
256
|
+
});
|
|
257
|
+
it('should track filter replacement warnings', () => {
|
|
258
|
+
// Filter life getting low
|
|
259
|
+
device.details = {
|
|
260
|
+
filter_life: {
|
|
261
|
+
percent: 15,
|
|
262
|
+
replace_indicator: false
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
expect(device.filterLife).toBe(15);
|
|
266
|
+
// Filter replacement needed
|
|
267
|
+
device.details = {
|
|
268
|
+
filter_life: {
|
|
269
|
+
percent: 5,
|
|
270
|
+
replace_indicator: true
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
expect(device.filterLife).toBe(5);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
describe('Air Quality History and Trends', () => {
|
|
277
|
+
it('should handle air quality improvements', () => {
|
|
278
|
+
// Poor air quality initially
|
|
279
|
+
device.details = {
|
|
280
|
+
air_quality: 4,
|
|
281
|
+
air_quality_value: 85,
|
|
282
|
+
filter_life: { percent: 40 }
|
|
283
|
+
};
|
|
284
|
+
expect(device.airQualityLevel).toBe(4);
|
|
285
|
+
// Air quality improves
|
|
286
|
+
device.details.air_quality = 2;
|
|
287
|
+
device.details.air_quality_value = 25;
|
|
288
|
+
expect(device.airQualityLevel).toBe(2);
|
|
289
|
+
expect(device.airQualityValue).toBe(25);
|
|
290
|
+
});
|
|
291
|
+
it('should handle air quality deterioration', () => {
|
|
292
|
+
// Good air quality initially
|
|
293
|
+
device.details = {
|
|
294
|
+
air_quality: 2,
|
|
295
|
+
air_quality_value: 20,
|
|
296
|
+
filter_life: { percent: 60 }
|
|
297
|
+
};
|
|
298
|
+
expect(device.airQualityLevel).toBe(2);
|
|
299
|
+
// Air quality worsens
|
|
300
|
+
device.details.air_quality = 4;
|
|
301
|
+
device.details.air_quality_value = 90;
|
|
302
|
+
expect(device.airQualityLevel).toBe(4);
|
|
303
|
+
expect(device.airQualityValue).toBe(90);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
describe('Error Handling and Edge Cases', () => {
|
|
307
|
+
it('should handle completely missing details', () => {
|
|
308
|
+
device.details = undefined;
|
|
309
|
+
expect(device.airQualityLevel).toBe('');
|
|
310
|
+
expect(device.airQualityValue).toBe(0);
|
|
311
|
+
expect(device.filterLife).toBe(0);
|
|
312
|
+
});
|
|
313
|
+
it('should handle partial API responses', () => {
|
|
314
|
+
device.details = {
|
|
315
|
+
mode: 'auto',
|
|
316
|
+
speed: 1
|
|
317
|
+
// All other fields missing
|
|
318
|
+
};
|
|
319
|
+
expect(device.mode).toBe('auto');
|
|
320
|
+
expect(device.speed).toBe(1);
|
|
321
|
+
expect(device.airQualityLevel).toBe('');
|
|
322
|
+
expect(device.airQualityValue).toBe(0);
|
|
323
|
+
expect(device.filterLife).toBe(0);
|
|
324
|
+
});
|
|
325
|
+
it('should handle network errors gracefully', () => {
|
|
326
|
+
// Simulate network error - details remain from last update
|
|
327
|
+
device.details = {
|
|
328
|
+
air_quality: 2,
|
|
329
|
+
air_quality_value: 30,
|
|
330
|
+
filter_life: { percent: 70 }
|
|
331
|
+
};
|
|
332
|
+
// Values preserved from last successful update
|
|
333
|
+
expect(device.airQualityLevel).toBe(2);
|
|
334
|
+
expect(device.airQualityValue).toBe(30);
|
|
335
|
+
expect(device.filterLife).toBe(70);
|
|
336
|
+
});
|
|
337
|
+
it('should handle invalid air quality levels', () => {
|
|
338
|
+
device.details = {
|
|
339
|
+
air_quality: 10, // Invalid level (should be 1-5)
|
|
340
|
+
air_quality_value: 50,
|
|
341
|
+
filter_life: { percent: 50 }
|
|
342
|
+
};
|
|
343
|
+
expect(device.airQualityLevel).toBe(10); // Return raw value for debugging
|
|
344
|
+
expect(device.airQualityValue).toBe(50);
|
|
345
|
+
});
|
|
346
|
+
it('should handle negative filter life percent', () => {
|
|
347
|
+
device.details = {
|
|
348
|
+
filter_life: {
|
|
349
|
+
percent: -10,
|
|
350
|
+
replace_indicator: true
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
expect(device.filterLife).toBe(-10); // Allow for error detection
|
|
354
|
+
});
|
|
355
|
+
it('should handle filter life percent over 100', () => {
|
|
356
|
+
device.details = {
|
|
357
|
+
filter_life: {
|
|
358
|
+
percent: 150,
|
|
359
|
+
replace_indicator: false
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
expect(device.filterLife).toBe(150); // Allow for error detection
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|