tsvesync 1.0.75 → 1.0.77
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/dist/lib/fans/airBaseV2.d.ts +26 -0
- package/dist/lib/fans/airBaseV2.js +260 -3
- package/package.json +1 -1
- package/src/lib/fans/airBaseV2.ts +320 -3
|
@@ -29,6 +29,11 @@ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
29
29
|
duration: number;
|
|
30
30
|
action: string;
|
|
31
31
|
} | null);
|
|
32
|
+
/**
|
|
33
|
+
* Check response for Vital series devices
|
|
34
|
+
* These devices return success at API level but may have inner errors
|
|
35
|
+
*/
|
|
36
|
+
protected checkVitalResponse(response: any, status: number, method: string): boolean;
|
|
32
37
|
/**
|
|
33
38
|
* Get device details
|
|
34
39
|
*/
|
|
@@ -37,6 +42,27 @@ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
37
42
|
* Build purifier dictionary
|
|
38
43
|
*/
|
|
39
44
|
protected buildPurifierDict(devDict: Record<string, any>): void;
|
|
45
|
+
/**
|
|
46
|
+
* Check if the current device is a Vital series model
|
|
47
|
+
* @returns true if this is a LAP-V102S or LAP-V201S device
|
|
48
|
+
*/
|
|
49
|
+
private isVitalSeries;
|
|
50
|
+
/**
|
|
51
|
+
* Override turn on method to handle Vital series special cases
|
|
52
|
+
*/
|
|
53
|
+
turnOn(): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Override turn off method to handle Vital series special cases
|
|
56
|
+
*/
|
|
57
|
+
turnOff(): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Override setMode method to handle Vital series special cases
|
|
60
|
+
*/
|
|
61
|
+
setMode(mode: string): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Override changeFanSpeed method to handle Vital series special cases
|
|
64
|
+
*/
|
|
65
|
+
changeFanSpeed(speed: number): Promise<boolean>;
|
|
40
66
|
/**
|
|
41
67
|
* Set auto preference
|
|
42
68
|
*/
|
|
@@ -45,13 +45,66 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
45
45
|
set timer(value) {
|
|
46
46
|
this._timer = value;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Check response for Vital series devices
|
|
50
|
+
* These devices return success at API level but may have inner errors
|
|
51
|
+
*/
|
|
52
|
+
checkVitalResponse(response, status, method) {
|
|
53
|
+
// First check basic response
|
|
54
|
+
if (!this.checkResponse([response, status], method)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
// Check if this is a Vital series device
|
|
58
|
+
const isVitalSeries = this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
|
|
59
|
+
if (!isVitalSeries) {
|
|
60
|
+
return true; // Not a Vital series device, standard check is enough
|
|
61
|
+
}
|
|
62
|
+
// For Vital series, we need to check the inner result code
|
|
63
|
+
if (response.result && response.result.code !== undefined && response.result.code !== 0) {
|
|
64
|
+
// Vital series often returns code -1 but still succeeds
|
|
65
|
+
// Let's update the state optimistically
|
|
66
|
+
logger_1.logger.debug(`Vital series device ${this.deviceName} returned inner code ${response.result.code} for ${method}`);
|
|
67
|
+
logger_1.logger.debug('This is normal for Vital series devices - proceeding with operation');
|
|
68
|
+
// Keep the device online despite inner error
|
|
69
|
+
this.connectionStatus = 'online';
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
48
74
|
/**
|
|
49
75
|
* Get device details
|
|
50
76
|
*/
|
|
51
77
|
async getDetails() {
|
|
78
|
+
var _a;
|
|
52
79
|
logger_1.logger.debug(`Getting details for device: ${this.deviceName}`);
|
|
53
80
|
const [head, body] = this.buildApiDict('getPurifierStatus');
|
|
54
81
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
82
|
+
// Check if this is a Vital series device (LAP-V102S or LAP-V201S)
|
|
83
|
+
const isVitalSeries = this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
|
|
84
|
+
// First check the outer response
|
|
85
|
+
if (!response || status !== 200) {
|
|
86
|
+
logger_1.logger.debug(`Error getting purifier details for ${this.deviceName}: Invalid response`);
|
|
87
|
+
this.connectionStatus = 'offline';
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
// For Vital series, we need special handling as they return inner code -1 for details
|
|
91
|
+
// but still provide valid status information
|
|
92
|
+
if (isVitalSeries && response.result) {
|
|
93
|
+
// Check if we have a valid getPurifierStatus response
|
|
94
|
+
if (response.code === 0 && ((_a = response.result) === null || _a === void 0 ? void 0 : _a.result)) {
|
|
95
|
+
const statusResponse = response.result.result;
|
|
96
|
+
// If we have valid status data, use it regardless of inner result code
|
|
97
|
+
logger_1.logger.debug(`Using status data for Vital series device: ${this.deviceName}`);
|
|
98
|
+
this.buildPurifierDict(statusResponse);
|
|
99
|
+
if (statusResponse.configuration) {
|
|
100
|
+
this.buildConfigDict(statusResponse.configuration);
|
|
101
|
+
}
|
|
102
|
+
// Keep device online since we have valid status
|
|
103
|
+
this.connectionStatus = 'online';
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Standard processing for non-Vital devices or when no status data is available
|
|
55
108
|
if (!this.checkResponse([response, status], 'getDetails') || !(response === null || response === void 0 ? void 0 : response.result)) {
|
|
56
109
|
logger_1.logger.debug('Error getting purifier details');
|
|
57
110
|
this.connectionStatus = 'offline';
|
|
@@ -116,6 +169,184 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
116
169
|
this.details.auto_preference_type = null;
|
|
117
170
|
}
|
|
118
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if the current device is a Vital series model
|
|
174
|
+
* @returns true if this is a LAP-V102S or LAP-V201S device
|
|
175
|
+
*/
|
|
176
|
+
isVitalSeries() {
|
|
177
|
+
return this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Override turn on method to handle Vital series special cases
|
|
181
|
+
*/
|
|
182
|
+
async turnOn() {
|
|
183
|
+
logger_1.logger.info(`Turning on device: ${this.deviceName}`);
|
|
184
|
+
const [head, body] = this.buildApiDict('setSwitch');
|
|
185
|
+
// For Vital series, use powerSwitch per YAML spec
|
|
186
|
+
// For other models, use the parent class implementation
|
|
187
|
+
if (this.isVitalSeries()) {
|
|
188
|
+
logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
189
|
+
body.payload.data = {
|
|
190
|
+
powerSwitch: 1,
|
|
191
|
+
switchIdx: 0
|
|
192
|
+
};
|
|
193
|
+
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
194
|
+
// Use Vital-specific response checking
|
|
195
|
+
const success = this.checkVitalResponse(response, status, 'turnOn');
|
|
196
|
+
if (success) {
|
|
197
|
+
this.deviceStatus = 'on';
|
|
198
|
+
// Force a details update to sync state
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
this.getDetails().catch(e => {
|
|
201
|
+
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
202
|
+
});
|
|
203
|
+
}, 1000);
|
|
204
|
+
}
|
|
205
|
+
return success;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Use parent implementation for non-Vital models
|
|
209
|
+
return super.turnOn();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Override turn off method to handle Vital series special cases
|
|
214
|
+
*/
|
|
215
|
+
async turnOff() {
|
|
216
|
+
logger_1.logger.info(`Turning off device: ${this.deviceName}`);
|
|
217
|
+
const [head, body] = this.buildApiDict('setSwitch');
|
|
218
|
+
// For Vital series, use powerSwitch per YAML spec
|
|
219
|
+
// For other models, use the parent class implementation
|
|
220
|
+
if (this.isVitalSeries()) {
|
|
221
|
+
logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
222
|
+
body.payload.data = {
|
|
223
|
+
powerSwitch: 0,
|
|
224
|
+
switchIdx: 0
|
|
225
|
+
};
|
|
226
|
+
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
227
|
+
// Use Vital-specific response checking
|
|
228
|
+
const success = this.checkVitalResponse(response, status, 'turnOff');
|
|
229
|
+
if (success) {
|
|
230
|
+
this.deviceStatus = 'off';
|
|
231
|
+
// Force a details update to sync state
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
this.getDetails().catch(e => {
|
|
234
|
+
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
235
|
+
});
|
|
236
|
+
}, 1000);
|
|
237
|
+
}
|
|
238
|
+
return success;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Use parent implementation for non-Vital models
|
|
242
|
+
return super.turnOff();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Override setMode method to handle Vital series special cases
|
|
247
|
+
*/
|
|
248
|
+
async setMode(mode) {
|
|
249
|
+
if (!this.modes.includes(mode)) {
|
|
250
|
+
const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
|
|
251
|
+
logger_1.logger.error(`${error} for device: ${this.deviceName}`);
|
|
252
|
+
throw new Error(error);
|
|
253
|
+
}
|
|
254
|
+
// Special handling for Core200S stays the same for all models
|
|
255
|
+
if (this.deviceType.includes('Core200S') && mode === 'auto') {
|
|
256
|
+
logger_1.logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
|
|
257
|
+
return this.setMode('manual');
|
|
258
|
+
}
|
|
259
|
+
// For Vital series, use special response handling
|
|
260
|
+
// For other models, use the parent implementation
|
|
261
|
+
if (this.isVitalSeries()) {
|
|
262
|
+
logger_1.logger.debug(`Using Vital series specific response handling for ${this.deviceName}`);
|
|
263
|
+
const [head, body] = this.buildApiDict('setPurifierMode');
|
|
264
|
+
body.payload.data = {
|
|
265
|
+
workMode: mode
|
|
266
|
+
};
|
|
267
|
+
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
268
|
+
const success = this.checkVitalResponse(response, status, 'setMode');
|
|
269
|
+
if (success) {
|
|
270
|
+
this.details.mode = mode;
|
|
271
|
+
this._mode = mode;
|
|
272
|
+
// Force a details update to sync state
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
this.getDetails().catch(e => {
|
|
275
|
+
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
276
|
+
});
|
|
277
|
+
}, 1000);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
logger_1.logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Use parent implementation for non-Vital models
|
|
287
|
+
return super.setMode(mode);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Override changeFanSpeed method to handle Vital series special cases
|
|
292
|
+
*/
|
|
293
|
+
async changeFanSpeed(speed) {
|
|
294
|
+
var _a;
|
|
295
|
+
const speeds = (_a = this.config.levels) !== null && _a !== void 0 ? _a : [];
|
|
296
|
+
if (speeds.length > 0 && !speeds.includes(speed)) {
|
|
297
|
+
logger_1.logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
logger_1.logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
|
|
301
|
+
const [head, body] = this.buildApiDict('setLevel');
|
|
302
|
+
// Specific handling for Vital series devices
|
|
303
|
+
if (this.isVitalSeries()) {
|
|
304
|
+
logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
305
|
+
// For Vital series, use levelIdx, levelType, and manualSpeedLevel per YAML spec
|
|
306
|
+
body.payload.data = {
|
|
307
|
+
levelIdx: 0,
|
|
308
|
+
levelType: 'wind',
|
|
309
|
+
manualSpeedLevel: speed
|
|
310
|
+
};
|
|
311
|
+
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
312
|
+
const success = this.checkVitalResponse(response, status, 'changeFanSpeed');
|
|
313
|
+
if (success) {
|
|
314
|
+
this.speed = speed;
|
|
315
|
+
this.details.manualSpeedLevel = speed;
|
|
316
|
+
// Force a details update to sync state
|
|
317
|
+
setTimeout(() => {
|
|
318
|
+
this.getDetails().catch(e => {
|
|
319
|
+
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
320
|
+
});
|
|
321
|
+
}, 1000);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
logger_1.logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// For non-Vital models, use id, level, mode, and type
|
|
331
|
+
body.payload.data = {
|
|
332
|
+
id: 0,
|
|
333
|
+
level: speed,
|
|
334
|
+
mode: 'manual',
|
|
335
|
+
type: 'wind'
|
|
336
|
+
};
|
|
337
|
+
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
338
|
+
const success = this.checkResponse([response, status], 'changeFanSpeed');
|
|
339
|
+
if (success) {
|
|
340
|
+
this.speed = speed;
|
|
341
|
+
this.details.manualSpeedLevel = speed;
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
logger_1.logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
119
350
|
/**
|
|
120
351
|
* Set auto preference
|
|
121
352
|
*/
|
|
@@ -130,9 +361,21 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
130
361
|
roomSize: roomSize
|
|
131
362
|
};
|
|
132
363
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
133
|
-
|
|
364
|
+
// Use the appropriate response checking method based on device type
|
|
365
|
+
const success = this.isVitalSeries()
|
|
366
|
+
? this.checkVitalResponse(response, status, 'setAutoPreference')
|
|
367
|
+
: this.checkResponse([response, status], 'setAutoPreference');
|
|
134
368
|
if (success) {
|
|
135
369
|
this.details.auto_preference = preference;
|
|
370
|
+
this.details.auto_preference_type = preference;
|
|
371
|
+
// Force a details update to sync state for Vital series
|
|
372
|
+
if (this.isVitalSeries()) {
|
|
373
|
+
setTimeout(() => {
|
|
374
|
+
this.getDetails().catch(e => {
|
|
375
|
+
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
376
|
+
});
|
|
377
|
+
}, 1000);
|
|
378
|
+
}
|
|
136
379
|
}
|
|
137
380
|
return success;
|
|
138
381
|
}
|
|
@@ -145,9 +388,20 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
145
388
|
lightDetectionSwitch: enabled ? 1 : 0
|
|
146
389
|
};
|
|
147
390
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
148
|
-
|
|
391
|
+
// Use the appropriate response checking method based on device type
|
|
392
|
+
const success = this.isVitalSeries()
|
|
393
|
+
? this.checkVitalResponse(response, status, 'setLightDetection')
|
|
394
|
+
: this.checkResponse([response, status], 'setLightDetection');
|
|
149
395
|
if (success) {
|
|
150
396
|
this.details.light_detection_switch = enabled;
|
|
397
|
+
// Force a details update to sync state for Vital series
|
|
398
|
+
if (this.isVitalSeries()) {
|
|
399
|
+
setTimeout(() => {
|
|
400
|
+
this.getDetails().catch(e => {
|
|
401
|
+
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
402
|
+
});
|
|
403
|
+
}, 1000);
|
|
404
|
+
}
|
|
151
405
|
}
|
|
152
406
|
return success;
|
|
153
407
|
}
|
|
@@ -215,7 +469,10 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
215
469
|
switchIdx: 0
|
|
216
470
|
};
|
|
217
471
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
218
|
-
|
|
472
|
+
// Use the appropriate response checking method based on device type
|
|
473
|
+
const success = this.isVitalSeries()
|
|
474
|
+
? this.checkVitalResponse(response, status, 'setOscillation')
|
|
475
|
+
: this.checkResponse([response, status], 'setOscillation');
|
|
219
476
|
if (success) {
|
|
220
477
|
this.details.oscillationState = toggle;
|
|
221
478
|
this.details.oscillationSwitch = toggle ? 1 : 0;
|
package/package.json
CHANGED
|
@@ -54,6 +54,37 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
54
54
|
this._timer = value;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Check response for Vital series devices
|
|
59
|
+
* These devices return success at API level but may have inner errors
|
|
60
|
+
*/
|
|
61
|
+
protected checkVitalResponse(response: any, status: number, method: string): boolean {
|
|
62
|
+
// First check basic response
|
|
63
|
+
if (!this.checkResponse([response, status], method)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check if this is a Vital series device
|
|
68
|
+
const isVitalSeries = this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
|
|
69
|
+
if (!isVitalSeries) {
|
|
70
|
+
return true; // Not a Vital series device, standard check is enough
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// For Vital series, we need to check the inner result code
|
|
74
|
+
if (response.result && response.result.code !== undefined && response.result.code !== 0) {
|
|
75
|
+
// Vital series often returns code -1 but still succeeds
|
|
76
|
+
// Let's update the state optimistically
|
|
77
|
+
logger.debug(`Vital series device ${this.deviceName} returned inner code ${response.result.code} for ${method}`);
|
|
78
|
+
logger.debug('This is normal for Vital series devices - proceeding with operation');
|
|
79
|
+
|
|
80
|
+
// Keep the device online despite inner error
|
|
81
|
+
this.connectionStatus = 'online';
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
57
88
|
/**
|
|
58
89
|
* Get device details
|
|
59
90
|
*/
|
|
@@ -68,6 +99,36 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
68
99
|
head
|
|
69
100
|
);
|
|
70
101
|
|
|
102
|
+
// Check if this is a Vital series device (LAP-V102S or LAP-V201S)
|
|
103
|
+
const isVitalSeries = this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
|
|
104
|
+
|
|
105
|
+
// First check the outer response
|
|
106
|
+
if (!response || status !== 200) {
|
|
107
|
+
logger.debug(`Error getting purifier details for ${this.deviceName}: Invalid response`);
|
|
108
|
+
this.connectionStatus = 'offline';
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// For Vital series, we need special handling as they return inner code -1 for details
|
|
113
|
+
// but still provide valid status information
|
|
114
|
+
if (isVitalSeries && response.result) {
|
|
115
|
+
// Check if we have a valid getPurifierStatus response
|
|
116
|
+
if (response.code === 0 && response.result?.result) {
|
|
117
|
+
const statusResponse = response.result.result;
|
|
118
|
+
|
|
119
|
+
// If we have valid status data, use it regardless of inner result code
|
|
120
|
+
logger.debug(`Using status data for Vital series device: ${this.deviceName}`);
|
|
121
|
+
this.buildPurifierDict(statusResponse);
|
|
122
|
+
if (statusResponse.configuration) {
|
|
123
|
+
this.buildConfigDict(statusResponse.configuration);
|
|
124
|
+
}
|
|
125
|
+
// Keep device online since we have valid status
|
|
126
|
+
this.connectionStatus = 'online';
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Standard processing for non-Vital devices or when no status data is available
|
|
71
132
|
if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
|
|
72
133
|
logger.debug('Error getting purifier details');
|
|
73
134
|
this.connectionStatus = 'offline';
|
|
@@ -135,6 +196,231 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
135
196
|
}
|
|
136
197
|
}
|
|
137
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Check if the current device is a Vital series model
|
|
201
|
+
* @returns true if this is a LAP-V102S or LAP-V201S device
|
|
202
|
+
*/
|
|
203
|
+
private isVitalSeries(): boolean {
|
|
204
|
+
return this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Override turn on method to handle Vital series special cases
|
|
209
|
+
*/
|
|
210
|
+
override async turnOn(): Promise<boolean> {
|
|
211
|
+
logger.info(`Turning on device: ${this.deviceName}`);
|
|
212
|
+
|
|
213
|
+
const [head, body] = this.buildApiDict('setSwitch');
|
|
214
|
+
|
|
215
|
+
// For Vital series, use powerSwitch per YAML spec
|
|
216
|
+
// For other models, use the parent class implementation
|
|
217
|
+
if (this.isVitalSeries()) {
|
|
218
|
+
logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
219
|
+
body.payload.data = {
|
|
220
|
+
powerSwitch: 1,
|
|
221
|
+
switchIdx: 0
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const [response, status] = await this.callApi(
|
|
225
|
+
'/cloud/v2/deviceManaged/bypassV2',
|
|
226
|
+
'post',
|
|
227
|
+
body,
|
|
228
|
+
head
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// Use Vital-specific response checking
|
|
232
|
+
const success = this.checkVitalResponse(response, status, 'turnOn');
|
|
233
|
+
if (success) {
|
|
234
|
+
this.deviceStatus = 'on';
|
|
235
|
+
|
|
236
|
+
// Force a details update to sync state
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
this.getDetails().catch(e => {
|
|
239
|
+
logger.debug(`Background state refresh failed: ${e}`);
|
|
240
|
+
});
|
|
241
|
+
}, 1000);
|
|
242
|
+
}
|
|
243
|
+
return success;
|
|
244
|
+
} else {
|
|
245
|
+
// Use parent implementation for non-Vital models
|
|
246
|
+
return super.turnOn();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Override turn off method to handle Vital series special cases
|
|
252
|
+
*/
|
|
253
|
+
override async turnOff(): Promise<boolean> {
|
|
254
|
+
logger.info(`Turning off device: ${this.deviceName}`);
|
|
255
|
+
|
|
256
|
+
const [head, body] = this.buildApiDict('setSwitch');
|
|
257
|
+
|
|
258
|
+
// For Vital series, use powerSwitch per YAML spec
|
|
259
|
+
// For other models, use the parent class implementation
|
|
260
|
+
if (this.isVitalSeries()) {
|
|
261
|
+
logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
262
|
+
body.payload.data = {
|
|
263
|
+
powerSwitch: 0,
|
|
264
|
+
switchIdx: 0
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const [response, status] = await this.callApi(
|
|
268
|
+
'/cloud/v2/deviceManaged/bypassV2',
|
|
269
|
+
'post',
|
|
270
|
+
body,
|
|
271
|
+
head
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Use Vital-specific response checking
|
|
275
|
+
const success = this.checkVitalResponse(response, status, 'turnOff');
|
|
276
|
+
if (success) {
|
|
277
|
+
this.deviceStatus = 'off';
|
|
278
|
+
|
|
279
|
+
// Force a details update to sync state
|
|
280
|
+
setTimeout(() => {
|
|
281
|
+
this.getDetails().catch(e => {
|
|
282
|
+
logger.debug(`Background state refresh failed: ${e}`);
|
|
283
|
+
});
|
|
284
|
+
}, 1000);
|
|
285
|
+
}
|
|
286
|
+
return success;
|
|
287
|
+
} else {
|
|
288
|
+
// Use parent implementation for non-Vital models
|
|
289
|
+
return super.turnOff();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Override setMode method to handle Vital series special cases
|
|
295
|
+
*/
|
|
296
|
+
override async setMode(mode: string): Promise<boolean> {
|
|
297
|
+
if (!this.modes.includes(mode as any)) {
|
|
298
|
+
const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
|
|
299
|
+
logger.error(`${error} for device: ${this.deviceName}`);
|
|
300
|
+
throw new Error(error);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Special handling for Core200S stays the same for all models
|
|
304
|
+
if (this.deviceType.includes('Core200S') && mode === 'auto') {
|
|
305
|
+
logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
|
|
306
|
+
return this.setMode('manual');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// For Vital series, use special response handling
|
|
310
|
+
// For other models, use the parent implementation
|
|
311
|
+
if (this.isVitalSeries()) {
|
|
312
|
+
logger.debug(`Using Vital series specific response handling for ${this.deviceName}`);
|
|
313
|
+
const [head, body] = this.buildApiDict('setPurifierMode');
|
|
314
|
+
body.payload.data = {
|
|
315
|
+
workMode: mode
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const [response, status] = await this.callApi(
|
|
319
|
+
'/cloud/v2/deviceManaged/bypassV2',
|
|
320
|
+
'post',
|
|
321
|
+
body,
|
|
322
|
+
head
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const success = this.checkVitalResponse(response, status, 'setMode');
|
|
326
|
+
if (success) {
|
|
327
|
+
this.details.mode = mode;
|
|
328
|
+
this._mode = mode;
|
|
329
|
+
|
|
330
|
+
// Force a details update to sync state
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
this.getDetails().catch(e => {
|
|
333
|
+
logger.debug(`Background state refresh failed: ${e}`);
|
|
334
|
+
});
|
|
335
|
+
}, 1000);
|
|
336
|
+
|
|
337
|
+
return true;
|
|
338
|
+
} else {
|
|
339
|
+
logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`);
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
// Use parent implementation for non-Vital models
|
|
344
|
+
return super.setMode(mode);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Override changeFanSpeed method to handle Vital series special cases
|
|
350
|
+
*/
|
|
351
|
+
override async changeFanSpeed(speed: number): Promise<boolean> {
|
|
352
|
+
const speeds = this.config.levels ?? [];
|
|
353
|
+
if (speeds.length > 0 && !speeds.includes(speed)) {
|
|
354
|
+
logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
|
|
359
|
+
|
|
360
|
+
const [head, body] = this.buildApiDict('setLevel');
|
|
361
|
+
|
|
362
|
+
// Specific handling for Vital series devices
|
|
363
|
+
if (this.isVitalSeries()) {
|
|
364
|
+
logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
365
|
+
// For Vital series, use levelIdx, levelType, and manualSpeedLevel per YAML spec
|
|
366
|
+
body.payload.data = {
|
|
367
|
+
levelIdx: 0,
|
|
368
|
+
levelType: 'wind',
|
|
369
|
+
manualSpeedLevel: speed
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const [response, status] = await this.callApi(
|
|
373
|
+
'/cloud/v2/deviceManaged/bypassV2',
|
|
374
|
+
'post',
|
|
375
|
+
body,
|
|
376
|
+
head
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const success = this.checkVitalResponse(response, status, 'changeFanSpeed');
|
|
380
|
+
if (success) {
|
|
381
|
+
this.speed = speed;
|
|
382
|
+
this.details.manualSpeedLevel = speed;
|
|
383
|
+
|
|
384
|
+
// Force a details update to sync state
|
|
385
|
+
setTimeout(() => {
|
|
386
|
+
this.getDetails().catch(e => {
|
|
387
|
+
logger.debug(`Background state refresh failed: ${e}`);
|
|
388
|
+
});
|
|
389
|
+
}, 1000);
|
|
390
|
+
|
|
391
|
+
return true;
|
|
392
|
+
} else {
|
|
393
|
+
logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
// For non-Vital models, use id, level, mode, and type
|
|
398
|
+
body.payload.data = {
|
|
399
|
+
id: 0,
|
|
400
|
+
level: speed,
|
|
401
|
+
mode: 'manual',
|
|
402
|
+
type: 'wind'
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const [response, status] = await this.callApi(
|
|
406
|
+
'/cloud/v2/deviceManaged/bypassV2',
|
|
407
|
+
'post',
|
|
408
|
+
body,
|
|
409
|
+
head
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const success = this.checkResponse([response, status], 'changeFanSpeed');
|
|
413
|
+
if (success) {
|
|
414
|
+
this.speed = speed;
|
|
415
|
+
this.details.manualSpeedLevel = speed;
|
|
416
|
+
return true;
|
|
417
|
+
} else {
|
|
418
|
+
logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
138
424
|
/**
|
|
139
425
|
* Set auto preference
|
|
140
426
|
*/
|
|
@@ -157,9 +443,23 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
157
443
|
head
|
|
158
444
|
);
|
|
159
445
|
|
|
160
|
-
|
|
446
|
+
// Use the appropriate response checking method based on device type
|
|
447
|
+
const success = this.isVitalSeries()
|
|
448
|
+
? this.checkVitalResponse(response, status, 'setAutoPreference')
|
|
449
|
+
: this.checkResponse([response, status], 'setAutoPreference');
|
|
450
|
+
|
|
161
451
|
if (success) {
|
|
162
452
|
this.details.auto_preference = preference;
|
|
453
|
+
this.details.auto_preference_type = preference;
|
|
454
|
+
|
|
455
|
+
// Force a details update to sync state for Vital series
|
|
456
|
+
if (this.isVitalSeries()) {
|
|
457
|
+
setTimeout(() => {
|
|
458
|
+
this.getDetails().catch(e => {
|
|
459
|
+
logger.debug(`Background state refresh failed: ${e}`);
|
|
460
|
+
});
|
|
461
|
+
}, 1000);
|
|
462
|
+
}
|
|
163
463
|
}
|
|
164
464
|
return success;
|
|
165
465
|
}
|
|
@@ -180,9 +480,22 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
180
480
|
head
|
|
181
481
|
);
|
|
182
482
|
|
|
183
|
-
|
|
483
|
+
// Use the appropriate response checking method based on device type
|
|
484
|
+
const success = this.isVitalSeries()
|
|
485
|
+
? this.checkVitalResponse(response, status, 'setLightDetection')
|
|
486
|
+
: this.checkResponse([response, status], 'setLightDetection');
|
|
487
|
+
|
|
184
488
|
if (success) {
|
|
185
489
|
this.details.light_detection_switch = enabled;
|
|
490
|
+
|
|
491
|
+
// Force a details update to sync state for Vital series
|
|
492
|
+
if (this.isVitalSeries()) {
|
|
493
|
+
setTimeout(() => {
|
|
494
|
+
this.getDetails().catch(e => {
|
|
495
|
+
logger.debug(`Background state refresh failed: ${e}`);
|
|
496
|
+
});
|
|
497
|
+
}, 1000);
|
|
498
|
+
}
|
|
186
499
|
}
|
|
187
500
|
return success;
|
|
188
501
|
}
|
|
@@ -266,7 +579,11 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
266
579
|
head
|
|
267
580
|
);
|
|
268
581
|
|
|
269
|
-
|
|
582
|
+
// Use the appropriate response checking method based on device type
|
|
583
|
+
const success = this.isVitalSeries()
|
|
584
|
+
? this.checkVitalResponse(response, status, 'setOscillation')
|
|
585
|
+
: this.checkResponse([response, status], 'setOscillation');
|
|
586
|
+
|
|
270
587
|
if (success) {
|
|
271
588
|
this.details.oscillationState = toggle;
|
|
272
589
|
this.details.oscillationSwitch = toggle ? 1 : 0;
|