tsvesync 1.0.76 → 1.0.78
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 +233 -3
- package/dist/lib/fans/towerFan.d.ts +22 -2
- package/dist/lib/fans/towerFan.js +105 -42
- package/dist/lib/vesyncFan.js +5 -5
- package/package.json +1 -1
- package/src/lib/fans/airBaseV2.ts +290 -3
- package/src/lib/fans/towerFan.ts +118 -43
- package/src/lib/vesyncFan.ts +5 -5
|
@@ -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,6 +45,32 @@ 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
|
*/
|
|
@@ -143,6 +169,184 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
143
169
|
this.details.auto_preference_type = null;
|
|
144
170
|
}
|
|
145
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
|
+
}
|
|
146
350
|
/**
|
|
147
351
|
* Set auto preference
|
|
148
352
|
*/
|
|
@@ -157,9 +361,21 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
157
361
|
roomSize: roomSize
|
|
158
362
|
};
|
|
159
363
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
160
|
-
|
|
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');
|
|
161
368
|
if (success) {
|
|
162
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
|
+
}
|
|
163
379
|
}
|
|
164
380
|
return success;
|
|
165
381
|
}
|
|
@@ -172,9 +388,20 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
172
388
|
lightDetectionSwitch: enabled ? 1 : 0
|
|
173
389
|
};
|
|
174
390
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
175
|
-
|
|
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');
|
|
176
395
|
if (success) {
|
|
177
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
|
+
}
|
|
178
405
|
}
|
|
179
406
|
return success;
|
|
180
407
|
}
|
|
@@ -242,7 +469,10 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
242
469
|
switchIdx: 0
|
|
243
470
|
};
|
|
244
471
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
245
|
-
|
|
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');
|
|
246
476
|
if (success) {
|
|
247
477
|
this.details.oscillationState = toggle;
|
|
248
478
|
this.details.oscillationSwitch = toggle ? 1 : 0;
|
|
@@ -4,7 +4,7 @@ import { VeSync } from '../vesync';
|
|
|
4
4
|
* VeSync Tower Fan Implementation
|
|
5
5
|
*/
|
|
6
6
|
export declare class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
7
|
-
protected readonly towerModes: readonly ["normal", "advancedSleep", "off"];
|
|
7
|
+
protected readonly towerModes: readonly ["normal", "auto", "advancedSleep", "turbo", "off"];
|
|
8
8
|
constructor(details: Record<string, any>, manager: VeSync);
|
|
9
9
|
/**
|
|
10
10
|
* Get device details
|
|
@@ -25,7 +25,7 @@ export declare class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
25
25
|
/**
|
|
26
26
|
* Set tower fan mode
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
modeToggle(mode: string): Promise<boolean>;
|
|
29
29
|
/**
|
|
30
30
|
* Change fan speed
|
|
31
31
|
*/
|
|
@@ -38,6 +38,14 @@ export declare class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
38
38
|
* Set manual mode - adapter to set mode to normal
|
|
39
39
|
*/
|
|
40
40
|
manual_mode(): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Set auto mode
|
|
43
|
+
*/
|
|
44
|
+
auto_mode(): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Set turbo mode
|
|
47
|
+
*/
|
|
48
|
+
turbo_mode(): Promise<boolean>;
|
|
41
49
|
/**
|
|
42
50
|
* Set advanced sleep mode
|
|
43
51
|
*/
|
|
@@ -50,6 +58,10 @@ export declare class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
50
58
|
* Override setMode to handle tower fan specific modes
|
|
51
59
|
*/
|
|
52
60
|
setMode(mode: string): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Alias for modeToggle to match Python naming convention
|
|
63
|
+
*/
|
|
64
|
+
mode_toggle(mode: string): Promise<boolean>;
|
|
53
65
|
/**
|
|
54
66
|
* Get current speed
|
|
55
67
|
*/
|
|
@@ -66,6 +78,10 @@ export declare class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
66
78
|
* Get mute status
|
|
67
79
|
*/
|
|
68
80
|
get muteState(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Get mute switch status
|
|
83
|
+
*/
|
|
84
|
+
get muteSwitch(): boolean;
|
|
69
85
|
/**
|
|
70
86
|
* Get temperature (in tenths of a degree)
|
|
71
87
|
*/
|
|
@@ -90,6 +106,10 @@ export declare class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
90
106
|
* Get error code
|
|
91
107
|
*/
|
|
92
108
|
get errorCode(): number;
|
|
109
|
+
/**
|
|
110
|
+
* Set oscillation state
|
|
111
|
+
*/
|
|
112
|
+
setOscillation(state: boolean): Promise<boolean>;
|
|
93
113
|
/**
|
|
94
114
|
* Turn oscillation on
|
|
95
115
|
*/
|
|
@@ -9,7 +9,7 @@ const logger_1 = require("../logger");
|
|
|
9
9
|
class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
10
10
|
constructor(details, manager) {
|
|
11
11
|
super(details, manager);
|
|
12
|
-
this.towerModes = ['normal', 'advancedSleep', 'off'];
|
|
12
|
+
this.towerModes = ['normal', 'auto', 'advancedSleep', 'turbo', 'off'];
|
|
13
13
|
logger_1.logger.debug(`Initialized VeSyncTowerFan device: ${this.deviceName}`);
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
@@ -30,26 +30,19 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
30
30
|
this.connectionStatus = 'offline';
|
|
31
31
|
return false;
|
|
32
32
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
humidity: deviceData.humidity || 0,
|
|
47
|
-
thermalComfort: deviceData.thermalComfort,
|
|
48
|
-
sleepPreference: deviceData.sleepPreference || {},
|
|
49
|
-
scheduleCount: deviceData.scheduleCount,
|
|
50
|
-
displayingType: deviceData.displayingType,
|
|
51
|
-
errorCode: deviceData.errorCode
|
|
52
|
-
};
|
|
33
|
+
const innerResult = innerResponse.result;
|
|
34
|
+
// Use the same methods as Python version
|
|
35
|
+
if (innerResult) {
|
|
36
|
+
this.buildPurifierDict(innerResult);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.connectionStatus = 'offline';
|
|
40
|
+
logger_1.logger.debug('error in inner result dict from tower fan');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (innerResult.configuration) {
|
|
44
|
+
this.buildConfigDict(innerResult.configuration);
|
|
45
|
+
}
|
|
53
46
|
logger_1.logger.debug(`Successfully got details for device: ${this.deviceName}`);
|
|
54
47
|
return true;
|
|
55
48
|
}
|
|
@@ -93,7 +86,7 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
93
86
|
/**
|
|
94
87
|
* Set tower fan mode
|
|
95
88
|
*/
|
|
96
|
-
async
|
|
89
|
+
async modeToggle(mode) {
|
|
97
90
|
if (!this.towerModes.includes(mode)) {
|
|
98
91
|
logger_1.logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.towerModes.join(', ')}`);
|
|
99
92
|
return false;
|
|
@@ -120,11 +113,12 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
120
113
|
* Change fan speed
|
|
121
114
|
*/
|
|
122
115
|
async changeFanSpeed(speed) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
var _a;
|
|
117
|
+
const speeds = (_a = this.config.levels) !== null && _a !== void 0 ? _a : [];
|
|
118
|
+
if (!speeds.includes(speed)) {
|
|
119
|
+
logger_1.logger.debug(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')}`);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
128
122
|
const [head, body] = this.buildApiDict('setLevel');
|
|
129
123
|
body.payload.data = {
|
|
130
124
|
levelIdx: 0,
|
|
@@ -146,7 +140,7 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
146
140
|
*/
|
|
147
141
|
async normal_mode() {
|
|
148
142
|
logger_1.logger.debug(`Setting normal mode for device: ${this.deviceName}`);
|
|
149
|
-
return this.
|
|
143
|
+
return this.modeToggle('normal');
|
|
150
144
|
}
|
|
151
145
|
/**
|
|
152
146
|
* Set manual mode - adapter to set mode to normal
|
|
@@ -155,12 +149,26 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
155
149
|
logger_1.logger.debug(`Setting manual mode for device: ${this.deviceName}`);
|
|
156
150
|
return this.normal_mode();
|
|
157
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Set auto mode
|
|
154
|
+
*/
|
|
155
|
+
async auto_mode() {
|
|
156
|
+
logger_1.logger.debug(`Setting auto mode for device: ${this.deviceName}`);
|
|
157
|
+
return this.modeToggle('auto');
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Set turbo mode
|
|
161
|
+
*/
|
|
162
|
+
async turbo_mode() {
|
|
163
|
+
logger_1.logger.debug(`Setting turbo mode for device: ${this.deviceName}`);
|
|
164
|
+
return this.modeToggle('turbo');
|
|
165
|
+
}
|
|
158
166
|
/**
|
|
159
167
|
* Set advanced sleep mode
|
|
160
168
|
*/
|
|
161
169
|
async advanced_sleep_mode() {
|
|
162
170
|
logger_1.logger.debug(`Setting advanced sleep mode for device: ${this.deviceName}`);
|
|
163
|
-
return this.
|
|
171
|
+
return this.modeToggle('advancedSleep');
|
|
164
172
|
}
|
|
165
173
|
/**
|
|
166
174
|
* Set sleep mode - adapter for advanced sleep mode
|
|
@@ -173,7 +181,13 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
173
181
|
* Override setMode to handle tower fan specific modes
|
|
174
182
|
*/
|
|
175
183
|
async setMode(mode) {
|
|
176
|
-
return this.
|
|
184
|
+
return this.modeToggle(mode);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Alias for modeToggle to match Python naming convention
|
|
188
|
+
*/
|
|
189
|
+
async mode_toggle(mode) {
|
|
190
|
+
return this.modeToggle(mode);
|
|
177
191
|
}
|
|
178
192
|
/**
|
|
179
193
|
* Get current speed
|
|
@@ -199,6 +213,12 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
199
213
|
get muteState() {
|
|
200
214
|
return Boolean(this.details.muteState);
|
|
201
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Get mute switch status
|
|
218
|
+
*/
|
|
219
|
+
get muteSwitch() {
|
|
220
|
+
return Boolean(this.details.muteSwitch);
|
|
221
|
+
}
|
|
202
222
|
/**
|
|
203
223
|
* Get temperature (in tenths of a degree)
|
|
204
224
|
*/
|
|
@@ -235,6 +255,26 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
235
255
|
get errorCode() {
|
|
236
256
|
return this.details.errorCode || 0;
|
|
237
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Set oscillation state
|
|
260
|
+
*/
|
|
261
|
+
async setOscillation(state) {
|
|
262
|
+
const [head, body] = this.buildApiDict('setSwitch');
|
|
263
|
+
body.payload.data = {
|
|
264
|
+
oscillationSwitch: state ? 1 : 0,
|
|
265
|
+
switchIdx: 0
|
|
266
|
+
};
|
|
267
|
+
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
268
|
+
const success = this.checkResponse([response, status], 'setOscillation');
|
|
269
|
+
if (success) {
|
|
270
|
+
this.details.oscillationSwitch = state ? 1 : 0;
|
|
271
|
+
this.details.oscillationState = state ? 1 : 0;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
logger_1.logger.debug('Error setting oscillation state');
|
|
275
|
+
}
|
|
276
|
+
return success;
|
|
277
|
+
}
|
|
238
278
|
/**
|
|
239
279
|
* Turn oscillation on
|
|
240
280
|
*/
|
|
@@ -257,20 +297,43 @@ class VeSyncTowerFan extends airBaseV2_1.VeSyncAirBaseV2 {
|
|
|
257
297
|
['Speed: ', this.speed],
|
|
258
298
|
['Fan Speed Level: ', this.details.fanSpeedLevel || 0],
|
|
259
299
|
['Display: ', this.displayState],
|
|
260
|
-
['Display Switch: ', this.details.screenSwitch],
|
|
261
300
|
['Oscillation: ', this.oscillationState],
|
|
262
|
-
['
|
|
263
|
-
['Mute: ', this.muteState],
|
|
264
|
-
['Mute Switch: ', this.details.muteSwitch],
|
|
265
|
-
['Timer: ', this.timer],
|
|
266
|
-
['Temperature: ', this.temperature / 10, '°C'],
|
|
267
|
-
['Humidity: ', this.humidity, '%'],
|
|
268
|
-
['Thermal Comfort: ', this.thermalComfort],
|
|
269
|
-
['Sleep Preference: ', JSON.stringify(this.sleepPreference)],
|
|
270
|
-
['Schedule Count: ', this.scheduleCount],
|
|
271
|
-
['Displaying Type: ', this.details.displayingType],
|
|
272
|
-
['Error Code: ', this.errorCode]
|
|
301
|
+
['Timer: ', this.timer]
|
|
273
302
|
];
|
|
303
|
+
// Add optional properties only if they exist
|
|
304
|
+
if (this.details.screenSwitch !== undefined) {
|
|
305
|
+
info.push(['Display Switch: ', this.details.screenSwitch]);
|
|
306
|
+
}
|
|
307
|
+
if (this.details.oscillationSwitch !== undefined) {
|
|
308
|
+
info.push(['Oscillation Switch: ', this.details.oscillationSwitch]);
|
|
309
|
+
}
|
|
310
|
+
if (this.details.muteState !== undefined) {
|
|
311
|
+
info.push(['Mute: ', this.muteState]);
|
|
312
|
+
}
|
|
313
|
+
if (this.details.muteSwitch !== undefined) {
|
|
314
|
+
info.push(['Mute Switch: ', this.muteSwitch]);
|
|
315
|
+
}
|
|
316
|
+
if (this.temperature > 0) {
|
|
317
|
+
info.push(['Temperature: ', this.temperature / 10, '°C']);
|
|
318
|
+
}
|
|
319
|
+
if (this.humidity > 0) {
|
|
320
|
+
info.push(['Humidity: ', this.humidity, '%']);
|
|
321
|
+
}
|
|
322
|
+
if (this.thermalComfort > 0) {
|
|
323
|
+
info.push(['Thermal Comfort: ', this.thermalComfort]);
|
|
324
|
+
}
|
|
325
|
+
if (this.sleepPreference && Object.keys(this.sleepPreference).length > 0) {
|
|
326
|
+
info.push(['Sleep Preference: ', JSON.stringify(this.sleepPreference)]);
|
|
327
|
+
}
|
|
328
|
+
if (this.scheduleCount > 0) {
|
|
329
|
+
info.push(['Schedule Count: ', this.scheduleCount]);
|
|
330
|
+
}
|
|
331
|
+
if (this.details.displayingType !== undefined) {
|
|
332
|
+
info.push(['Displaying Type: ', this.details.displayingType]);
|
|
333
|
+
}
|
|
334
|
+
if (this.errorCode !== 0) {
|
|
335
|
+
info.push(['Error Code: ', this.errorCode]);
|
|
336
|
+
}
|
|
274
337
|
for (const [key, value, unit = ''] of info) {
|
|
275
338
|
logger_1.logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
|
|
276
339
|
}
|
package/dist/lib/vesyncFan.js
CHANGED
|
@@ -165,25 +165,25 @@ exports.fanConfig = {
|
|
|
165
165
|
features: ['display', 'child_lock', 'night_light', 'air_quality', 'timer', 'fan_rotate', 'fan_speed', 'auto_mode'],
|
|
166
166
|
levels: [1, 2, 3]
|
|
167
167
|
},
|
|
168
|
-
// LTF Series
|
|
168
|
+
// LTF Series (Smart Tower Fan)
|
|
169
169
|
'LTF-F422S-KEU': {
|
|
170
170
|
module: 'VeSyncTowerFan',
|
|
171
|
-
features: ['
|
|
171
|
+
features: ['fan_speed'],
|
|
172
172
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
173
173
|
},
|
|
174
174
|
'LTF-F422S-WUSR': {
|
|
175
175
|
module: 'VeSyncTowerFan',
|
|
176
|
-
features: ['
|
|
176
|
+
features: ['fan_speed'],
|
|
177
177
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
178
178
|
},
|
|
179
179
|
'LTF-F422_WJP': {
|
|
180
180
|
module: 'VeSyncTowerFan',
|
|
181
|
-
features: ['
|
|
181
|
+
features: ['fan_speed'],
|
|
182
182
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
183
183
|
},
|
|
184
184
|
'LTF-F422S-WUS': {
|
|
185
185
|
module: 'VeSyncTowerFan',
|
|
186
|
-
features: ['
|
|
186
|
+
features: ['fan_speed'],
|
|
187
187
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
188
188
|
},
|
|
189
189
|
// Classic Series
|
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
|
*/
|
|
@@ -165,6 +196,231 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
165
196
|
}
|
|
166
197
|
}
|
|
167
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
|
+
|
|
168
424
|
/**
|
|
169
425
|
* Set auto preference
|
|
170
426
|
*/
|
|
@@ -187,9 +443,23 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
187
443
|
head
|
|
188
444
|
);
|
|
189
445
|
|
|
190
|
-
|
|
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
|
+
|
|
191
451
|
if (success) {
|
|
192
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
|
+
}
|
|
193
463
|
}
|
|
194
464
|
return success;
|
|
195
465
|
}
|
|
@@ -210,9 +480,22 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
210
480
|
head
|
|
211
481
|
);
|
|
212
482
|
|
|
213
|
-
|
|
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
|
+
|
|
214
488
|
if (success) {
|
|
215
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
|
+
}
|
|
216
499
|
}
|
|
217
500
|
return success;
|
|
218
501
|
}
|
|
@@ -296,7 +579,11 @@ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
296
579
|
head
|
|
297
580
|
);
|
|
298
581
|
|
|
299
|
-
|
|
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
|
+
|
|
300
587
|
if (success) {
|
|
301
588
|
this.details.oscillationState = toggle;
|
|
302
589
|
this.details.oscillationSwitch = toggle ? 1 : 0;
|
package/src/lib/fans/towerFan.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { logger } from '../logger';
|
|
|
6
6
|
* VeSync Tower Fan Implementation
|
|
7
7
|
*/
|
|
8
8
|
export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
9
|
-
protected readonly towerModes = ['normal', 'advancedSleep', 'off'] as const;
|
|
9
|
+
protected readonly towerModes = ['normal', 'auto', 'advancedSleep', 'turbo', 'off'] as const;
|
|
10
10
|
|
|
11
11
|
constructor(details: Record<string, any>, manager: VeSync) {
|
|
12
12
|
super(details, manager);
|
|
@@ -40,26 +40,20 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
thermalComfort: deviceData.thermalComfort,
|
|
58
|
-
sleepPreference: deviceData.sleepPreference || {},
|
|
59
|
-
scheduleCount: deviceData.scheduleCount,
|
|
60
|
-
displayingType: deviceData.displayingType,
|
|
61
|
-
errorCode: deviceData.errorCode
|
|
62
|
-
};
|
|
43
|
+
const innerResult = innerResponse.result;
|
|
44
|
+
|
|
45
|
+
// Use the same methods as Python version
|
|
46
|
+
if (innerResult) {
|
|
47
|
+
this.buildPurifierDict(innerResult);
|
|
48
|
+
} else {
|
|
49
|
+
this.connectionStatus = 'offline';
|
|
50
|
+
logger.debug('error in inner result dict from tower fan');
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (innerResult.configuration) {
|
|
55
|
+
this.buildConfigDict(innerResult.configuration);
|
|
56
|
+
}
|
|
63
57
|
|
|
64
58
|
logger.debug(`Successfully got details for device: ${this.deviceName}`);
|
|
65
59
|
return true;
|
|
@@ -115,7 +109,7 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
115
109
|
/**
|
|
116
110
|
* Set tower fan mode
|
|
117
111
|
*/
|
|
118
|
-
async
|
|
112
|
+
async modeToggle(mode: string): Promise<boolean> {
|
|
119
113
|
if (!this.towerModes.includes(mode as any)) {
|
|
120
114
|
logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.towerModes.join(', ')}`);
|
|
121
115
|
return false;
|
|
@@ -151,11 +145,11 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
151
145
|
* Change fan speed
|
|
152
146
|
*/
|
|
153
147
|
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
148
|
+
const speeds = this.config.levels ?? [];
|
|
149
|
+
if (!speeds.includes(speed)) {
|
|
150
|
+
logger.debug(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')}`);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
159
153
|
|
|
160
154
|
const [head, body] = this.buildApiDict('setLevel');
|
|
161
155
|
body.payload.data = {
|
|
@@ -185,7 +179,7 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
185
179
|
*/
|
|
186
180
|
async normal_mode(): Promise<boolean> {
|
|
187
181
|
logger.debug(`Setting normal mode for device: ${this.deviceName}`);
|
|
188
|
-
return this.
|
|
182
|
+
return this.modeToggle('normal');
|
|
189
183
|
}
|
|
190
184
|
|
|
191
185
|
/**
|
|
@@ -196,12 +190,28 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
196
190
|
return this.normal_mode();
|
|
197
191
|
}
|
|
198
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Set auto mode
|
|
195
|
+
*/
|
|
196
|
+
async auto_mode(): Promise<boolean> {
|
|
197
|
+
logger.debug(`Setting auto mode for device: ${this.deviceName}`);
|
|
198
|
+
return this.modeToggle('auto');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Set turbo mode
|
|
203
|
+
*/
|
|
204
|
+
async turbo_mode(): Promise<boolean> {
|
|
205
|
+
logger.debug(`Setting turbo mode for device: ${this.deviceName}`);
|
|
206
|
+
return this.modeToggle('turbo');
|
|
207
|
+
}
|
|
208
|
+
|
|
199
209
|
/**
|
|
200
210
|
* Set advanced sleep mode
|
|
201
211
|
*/
|
|
202
212
|
async advanced_sleep_mode(): Promise<boolean> {
|
|
203
213
|
logger.debug(`Setting advanced sleep mode for device: ${this.deviceName}`);
|
|
204
|
-
return this.
|
|
214
|
+
return this.modeToggle('advancedSleep');
|
|
205
215
|
}
|
|
206
216
|
|
|
207
217
|
/**
|
|
@@ -216,7 +226,14 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
216
226
|
* Override setMode to handle tower fan specific modes
|
|
217
227
|
*/
|
|
218
228
|
async setMode(mode: string): Promise<boolean> {
|
|
219
|
-
return this.
|
|
229
|
+
return this.modeToggle(mode);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Alias for modeToggle to match Python naming convention
|
|
234
|
+
*/
|
|
235
|
+
async mode_toggle(mode: string): Promise<boolean> {
|
|
236
|
+
return this.modeToggle(mode);
|
|
220
237
|
}
|
|
221
238
|
|
|
222
239
|
/**
|
|
@@ -247,6 +264,13 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
247
264
|
return Boolean(this.details.muteState);
|
|
248
265
|
}
|
|
249
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Get mute switch status
|
|
269
|
+
*/
|
|
270
|
+
get muteSwitch(): boolean {
|
|
271
|
+
return Boolean(this.details.muteSwitch);
|
|
272
|
+
}
|
|
273
|
+
|
|
250
274
|
/**
|
|
251
275
|
* Get temperature (in tenths of a degree)
|
|
252
276
|
*/
|
|
@@ -289,6 +313,33 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
289
313
|
return this.details.errorCode || 0;
|
|
290
314
|
}
|
|
291
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Set oscillation state
|
|
318
|
+
*/
|
|
319
|
+
async setOscillation(state: boolean): Promise<boolean> {
|
|
320
|
+
const [head, body] = this.buildApiDict('setSwitch');
|
|
321
|
+
body.payload.data = {
|
|
322
|
+
oscillationSwitch: state ? 1 : 0,
|
|
323
|
+
switchIdx: 0
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const [response, status] = await this.callApi(
|
|
327
|
+
'/cloud/v2/deviceManaged/bypassV2',
|
|
328
|
+
'post',
|
|
329
|
+
body,
|
|
330
|
+
head
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const success = this.checkResponse([response, status], 'setOscillation');
|
|
334
|
+
if (success) {
|
|
335
|
+
this.details.oscillationSwitch = state ? 1 : 0;
|
|
336
|
+
this.details.oscillationState = state ? 1 : 0;
|
|
337
|
+
} else {
|
|
338
|
+
logger.debug('Error setting oscillation state');
|
|
339
|
+
}
|
|
340
|
+
return success;
|
|
341
|
+
}
|
|
342
|
+
|
|
292
343
|
/**
|
|
293
344
|
* Turn oscillation on
|
|
294
345
|
*/
|
|
@@ -308,26 +359,50 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
308
359
|
*/
|
|
309
360
|
override display(): void {
|
|
310
361
|
super.display();
|
|
311
|
-
const info = [
|
|
362
|
+
const info: Array<[string, any, string?]> = [
|
|
312
363
|
['Mode: ', this.details.workMode || ''],
|
|
313
364
|
['Speed: ', this.speed],
|
|
314
365
|
['Fan Speed Level: ', this.details.fanSpeedLevel || 0],
|
|
315
366
|
['Display: ', this.displayState],
|
|
316
|
-
['Display Switch: ', this.details.screenSwitch],
|
|
317
367
|
['Oscillation: ', this.oscillationState],
|
|
318
|
-
['
|
|
319
|
-
['Mute: ', this.muteState],
|
|
320
|
-
['Mute Switch: ', this.details.muteSwitch],
|
|
321
|
-
['Timer: ', this.timer],
|
|
322
|
-
['Temperature: ', this.temperature / 10, '°C'],
|
|
323
|
-
['Humidity: ', this.humidity, '%'],
|
|
324
|
-
['Thermal Comfort: ', this.thermalComfort],
|
|
325
|
-
['Sleep Preference: ', JSON.stringify(this.sleepPreference)],
|
|
326
|
-
['Schedule Count: ', this.scheduleCount],
|
|
327
|
-
['Displaying Type: ', this.details.displayingType],
|
|
328
|
-
['Error Code: ', this.errorCode]
|
|
368
|
+
['Timer: ', this.timer]
|
|
329
369
|
];
|
|
330
370
|
|
|
371
|
+
// Add optional properties only if they exist
|
|
372
|
+
if (this.details.screenSwitch !== undefined) {
|
|
373
|
+
info.push(['Display Switch: ', this.details.screenSwitch]);
|
|
374
|
+
}
|
|
375
|
+
if (this.details.oscillationSwitch !== undefined) {
|
|
376
|
+
info.push(['Oscillation Switch: ', this.details.oscillationSwitch]);
|
|
377
|
+
}
|
|
378
|
+
if (this.details.muteState !== undefined) {
|
|
379
|
+
info.push(['Mute: ', this.muteState]);
|
|
380
|
+
}
|
|
381
|
+
if (this.details.muteSwitch !== undefined) {
|
|
382
|
+
info.push(['Mute Switch: ', this.muteSwitch]);
|
|
383
|
+
}
|
|
384
|
+
if (this.temperature > 0) {
|
|
385
|
+
info.push(['Temperature: ', this.temperature / 10, '°C']);
|
|
386
|
+
}
|
|
387
|
+
if (this.humidity > 0) {
|
|
388
|
+
info.push(['Humidity: ', this.humidity, '%']);
|
|
389
|
+
}
|
|
390
|
+
if (this.thermalComfort > 0) {
|
|
391
|
+
info.push(['Thermal Comfort: ', this.thermalComfort]);
|
|
392
|
+
}
|
|
393
|
+
if (this.sleepPreference && Object.keys(this.sleepPreference).length > 0) {
|
|
394
|
+
info.push(['Sleep Preference: ', JSON.stringify(this.sleepPreference)]);
|
|
395
|
+
}
|
|
396
|
+
if (this.scheduleCount > 0) {
|
|
397
|
+
info.push(['Schedule Count: ', this.scheduleCount]);
|
|
398
|
+
}
|
|
399
|
+
if (this.details.displayingType !== undefined) {
|
|
400
|
+
info.push(['Displaying Type: ', this.details.displayingType]);
|
|
401
|
+
}
|
|
402
|
+
if (this.errorCode !== 0) {
|
|
403
|
+
info.push(['Error Code: ', this.errorCode]);
|
|
404
|
+
}
|
|
405
|
+
|
|
331
406
|
for (const [key, value, unit = ''] of info) {
|
|
332
407
|
logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
|
|
333
408
|
}
|
package/src/lib/vesyncFan.ts
CHANGED
|
@@ -175,25 +175,25 @@ export const fanConfig: FanConfig = {
|
|
|
175
175
|
levels: [1, 2, 3]
|
|
176
176
|
},
|
|
177
177
|
|
|
178
|
-
// LTF Series
|
|
178
|
+
// LTF Series (Smart Tower Fan)
|
|
179
179
|
'LTF-F422S-KEU': {
|
|
180
180
|
module: 'VeSyncTowerFan',
|
|
181
|
-
features: ['
|
|
181
|
+
features: ['fan_speed'],
|
|
182
182
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
183
183
|
},
|
|
184
184
|
'LTF-F422S-WUSR': {
|
|
185
185
|
module: 'VeSyncTowerFan',
|
|
186
|
-
features: ['
|
|
186
|
+
features: ['fan_speed'],
|
|
187
187
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
188
188
|
},
|
|
189
189
|
'LTF-F422_WJP': {
|
|
190
190
|
module: 'VeSyncTowerFan',
|
|
191
|
-
features: ['
|
|
191
|
+
features: ['fan_speed'],
|
|
192
192
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
193
193
|
},
|
|
194
194
|
'LTF-F422S-WUS': {
|
|
195
195
|
module: 'VeSyncTowerFan',
|
|
196
|
-
features: ['
|
|
196
|
+
features: ['fan_speed'],
|
|
197
197
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
198
198
|
},
|
|
199
199
|
|