tsvesync 1.6.1 → 1.7.0
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 +9 -1
- package/api/vesyncfan/LTF-F422S-KEU.yaml +152 -108
- package/api/vesyncfan/LTF-F422S-WJP.yaml +209 -0
- package/api/vesyncfan/LTF-F422S-WUS.yaml +152 -108
- package/api/vesyncfan/LTF-F422S-WUSR.yaml +155 -113
- package/api/vesyncfan/LTF-F422_WJP.yaml +152 -108
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/lib/fans/index.js +1 -0
- package/dist/lib/fans/towerFan.d.ts +29 -81
- package/dist/lib/fans/towerFan.js +158 -222
- package/dist/lib/vesyncFan.js +14 -4
- package/package.json +3 -2
- package/src/index.ts +3 -0
- package/src/lib/fans/index.ts +1 -0
- package/src/lib/fans/towerFan.ts +133 -260
- package/src/lib/vesyncFan.ts +14 -4
package/src/lib/fans/towerFan.ts
CHANGED
|
@@ -2,310 +2,183 @@ import { VeSyncAirBaseV2 } from './airBaseV2';
|
|
|
2
2
|
import { VeSync } from '../vesync';
|
|
3
3
|
import { logger } from '../logger';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* VeSync Tower Fan Implementation
|
|
7
|
-
*/
|
|
5
|
+
/** LTF-F422S tower fans use different commands from bypassV2 purifiers. */
|
|
8
6
|
export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
9
|
-
protected readonly towerModes = ['normal', 'advancedSleep', 'off'] as const;
|
|
7
|
+
protected readonly towerModes = ['normal', 'auto', 'advancedSleep', 'turbo', 'off'] as const;
|
|
8
|
+
private timerId?: number;
|
|
9
|
+
private timerDeadline?: number;
|
|
10
10
|
|
|
11
11
|
constructor(details: Record<string, any>, manager: VeSync) {
|
|
12
12
|
super(details, manager);
|
|
13
|
-
logger.debug(`Initialized VeSyncTowerFan device: ${this.deviceName}`);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const [response, status] = await this.callApi(
|
|
24
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
25
|
-
'post',
|
|
26
|
-
body,
|
|
27
|
-
head
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
|
|
31
|
-
logger.debug('Error getting tower fan details');
|
|
32
|
-
this.connectionStatus = 'offline';
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const innerResponse = response.result;
|
|
37
|
-
if (innerResponse.code !== 0 || !innerResponse.result) {
|
|
38
|
-
logger.debug('Error in inner response from tower fan');
|
|
39
|
-
this.connectionStatus = 'offline';
|
|
15
|
+
/** A successful cloud envelope can still contain a rejected device command. */
|
|
16
|
+
protected override checkResponse(response: [any, number], method: string): boolean {
|
|
17
|
+
if (!super.checkResponse(response, method)) return false;
|
|
18
|
+
const result = response[0]?.result;
|
|
19
|
+
if (!result || result.code !== 0) {
|
|
20
|
+
this.logError(method, `Device error code ${result?.code ?? 'missing'}: ${result?.msg ?? 'Invalid device response'}`);
|
|
40
21
|
return false;
|
|
41
22
|
}
|
|
42
|
-
|
|
43
|
-
const deviceData = innerResponse.result;
|
|
44
|
-
this.deviceStatus = deviceData.powerSwitch === 1 ? 'on' : 'off';
|
|
45
|
-
this.details = {
|
|
46
|
-
powerSwitch: deviceData.powerSwitch,
|
|
47
|
-
workMode: deviceData.workMode,
|
|
48
|
-
manualSpeedLevel: deviceData.manualSpeedLevel,
|
|
49
|
-
fanSpeedLevel: deviceData.fanSpeedLevel,
|
|
50
|
-
screenState: deviceData.screenState,
|
|
51
|
-
screenSwitch: deviceData.screenSwitch,
|
|
52
|
-
oscillationState: deviceData.oscillationState,
|
|
53
|
-
oscillationSwitch: deviceData.oscillationSwitch,
|
|
54
|
-
timerRemain: deviceData.timerRemain,
|
|
55
|
-
temperature: deviceData.temperature || 0,
|
|
56
|
-
humidity: deviceData.humidity || 0,
|
|
57
|
-
thermalComfort: deviceData.thermalComfort,
|
|
58
|
-
sleepPreference: deviceData.sleepPreference || {},
|
|
59
|
-
scheduleCount: deviceData.scheduleCount,
|
|
60
|
-
displayingType: deviceData.displayingType,
|
|
61
|
-
errorCode: deviceData.errorCode
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
logger.debug(`Successfully got details for device: ${this.deviceName}`);
|
|
65
23
|
return true;
|
|
66
24
|
}
|
|
67
25
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const [head, body] = this.buildApiDict('setSwitch');
|
|
78
|
-
body.payload.data = {
|
|
79
|
-
powerSwitch: toggle ? 1 : 0,
|
|
80
|
-
switchIdx: 0
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const [response, status] = await this.callApi(
|
|
84
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
85
|
-
'post',
|
|
86
|
-
body,
|
|
87
|
-
head
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
const success = this.checkResponse([response, status], 'toggleSwitch');
|
|
91
|
-
if (success) {
|
|
92
|
-
this.deviceStatus = toggle ? 'on' : 'off';
|
|
93
|
-
} else {
|
|
94
|
-
logger.debug('Error toggling tower fan power');
|
|
95
|
-
}
|
|
96
|
-
return success;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Turn device on
|
|
101
|
-
*/
|
|
102
|
-
async turnOn(): Promise<boolean> {
|
|
103
|
-
logger.debug(`Turning on device: ${this.deviceName}`);
|
|
104
|
-
return this.toggleSwitch(true);
|
|
26
|
+
private async request(method: string, data: Record<string, any> = {}): Promise<Record<string, any> | null> {
|
|
27
|
+
const [head, body] = this.buildApiDict(method);
|
|
28
|
+
body.payload.data = data;
|
|
29
|
+
const response = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
30
|
+
if (!this.checkResponse(response, method)) return null;
|
|
31
|
+
this.connectionStatus = 'online';
|
|
32
|
+
return response[0].result.result ?? {};
|
|
105
33
|
}
|
|
106
34
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
logger.debug(`Turning off device: ${this.deviceName}`);
|
|
112
|
-
return this.toggleSwitch(false);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Set tower fan mode
|
|
117
|
-
*/
|
|
118
|
-
async mode_toggle(mode: string): Promise<boolean> {
|
|
119
|
-
if (!this.towerModes.includes(mode as any)) {
|
|
120
|
-
logger.debug(`Invalid mode: ${mode}. Must be one of: ${this.towerModes.join(', ')}`);
|
|
35
|
+
async getDetails(): Promise<boolean> {
|
|
36
|
+
const data = await this.request('getTowerFanStatus');
|
|
37
|
+
if (!data || (data.powerSwitch !== 0 && data.powerSwitch !== 1)) {
|
|
38
|
+
this.connectionStatus = 'offline';
|
|
121
39
|
return false;
|
|
122
40
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
workMode: mode
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const [response, status] = await this.callApi(
|
|
135
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
136
|
-
'post',
|
|
137
|
-
body,
|
|
138
|
-
head
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const success = this.checkResponse([response, status], 'mode_toggle');
|
|
142
|
-
if (success) {
|
|
143
|
-
this.details.workMode = mode;
|
|
144
|
-
} else {
|
|
145
|
-
logger.error(`Failed to set ${mode} mode for device: ${this.deviceName}`);
|
|
146
|
-
}
|
|
147
|
-
return success;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Change fan speed
|
|
152
|
-
*/
|
|
153
|
-
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
154
|
-
// const speeds = this.config.levels ?? [];
|
|
155
|
-
// if (!speeds.includes(speed)) {
|
|
156
|
-
// logger.debug(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')}`);
|
|
157
|
-
// return false;
|
|
158
|
-
// }
|
|
159
|
-
|
|
160
|
-
const [head, body] = this.buildApiDict('setLevel');
|
|
161
|
-
body.payload.data = {
|
|
162
|
-
levelIdx: 0,
|
|
163
|
-
levelType: 'wind',
|
|
164
|
-
manualSpeedLevel: speed
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const [response, status] = await this.callApi(
|
|
168
|
-
'/cloud/v2/deviceManaged/bypassV2',
|
|
169
|
-
'post',
|
|
170
|
-
body,
|
|
171
|
-
head
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
const success = this.checkResponse([response, status], 'changeFanSpeed');
|
|
175
|
-
if (success) {
|
|
176
|
-
this.details.manualSpeedLevel = speed;
|
|
177
|
-
} else {
|
|
178
|
-
logger.debug('Error setting fan speed');
|
|
179
|
-
}
|
|
180
|
-
return success;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Set normal mode
|
|
185
|
-
*/
|
|
186
|
-
async normal_mode(): Promise<boolean> {
|
|
187
|
-
logger.debug(`Setting normal mode for device: ${this.deviceName}`);
|
|
188
|
-
return this.mode_toggle('normal');
|
|
41
|
+
this.deviceStatus = data.powerSwitch === 1 ? 'on' : 'off';
|
|
42
|
+
this.details = { ...data };
|
|
43
|
+
this.mode = data.workMode ?? 'normal';
|
|
44
|
+
// A status poll cannot identify the timer. It may have been replaced in
|
|
45
|
+
// the app, so never carry an older command's id into cancellation.
|
|
46
|
+
this.timerId = undefined;
|
|
47
|
+
this.updateTimerState(data.timerRemain ?? 0, this.deviceStatus === 'on' ? 'off' : 'on');
|
|
48
|
+
return true;
|
|
189
49
|
}
|
|
190
50
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return
|
|
51
|
+
async toggleSwitch(toggle: boolean): Promise<boolean> {
|
|
52
|
+
if (typeof toggle !== 'boolean') return false;
|
|
53
|
+
if (!await this.request('setSwitch', { powerSwitch: Number(toggle), switchIdx: 0 })) return false;
|
|
54
|
+
this.deviceStatus = toggle ? 'on' : 'off';
|
|
55
|
+
this.details.powerSwitch = Number(toggle);
|
|
56
|
+
return true;
|
|
197
57
|
}
|
|
198
58
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
*/
|
|
202
|
-
async advanced_sleep_mode(): Promise<boolean> {
|
|
203
|
-
logger.debug(`Setting advanced sleep mode for device: ${this.deviceName}`);
|
|
204
|
-
return this.mode_toggle('advancedSleep');
|
|
205
|
-
}
|
|
59
|
+
async turnOn(): Promise<boolean> { return this.toggleSwitch(true); }
|
|
60
|
+
async turnOff(): Promise<boolean> { return this.toggleSwitch(false); }
|
|
206
61
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
62
|
+
async mode_toggle(mode: string): Promise<boolean> {
|
|
63
|
+
const apiMode = mode === 'sleep' ? 'advancedSleep' : mode === 'manual' ? 'normal' : mode;
|
|
64
|
+
if (!this.towerModes.includes(apiMode as typeof this.towerModes[number])) return false;
|
|
65
|
+
if (apiMode === 'off') return this.turnOff();
|
|
66
|
+
if (!await this.request('setTowerFanMode', { workMode: apiMode })) return false;
|
|
67
|
+
this.mode = apiMode;
|
|
68
|
+
return true;
|
|
213
69
|
}
|
|
214
70
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
async
|
|
219
|
-
|
|
220
|
-
}
|
|
71
|
+
async setMode(mode: string): Promise<boolean> { return this.mode_toggle(mode); }
|
|
72
|
+
async normal_mode(): Promise<boolean> { return this.setMode('normal'); }
|
|
73
|
+
async manual_mode(): Promise<boolean> { return this.normal_mode(); }
|
|
74
|
+
async advanced_sleep_mode(): Promise<boolean> { return this.setMode('sleep'); }
|
|
75
|
+
async sleep_mode(): Promise<boolean> { return this.advanced_sleep_mode(); }
|
|
221
76
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return
|
|
77
|
+
async changeFanSpeed(speed: number): Promise<boolean> {
|
|
78
|
+
if (!Number.isInteger(speed) || !(this.config.levels ?? []).includes(speed)) return false;
|
|
79
|
+
if (!await this.request('setLevel', { levelIdx: 0, levelType: 'wind', manualSpeedLevel: speed })) return false;
|
|
80
|
+
this.details.manualSpeedLevel = speed;
|
|
81
|
+
return true;
|
|
227
82
|
}
|
|
228
83
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
84
|
+
async setOscillation(toggle: boolean): Promise<boolean> {
|
|
85
|
+
if (typeof toggle !== 'boolean') return false;
|
|
86
|
+
if (!await this.request('setOscillationSwitch', { oscillationSwitch: Number(toggle) })) return false;
|
|
87
|
+
this.details.oscillationSwitch = Number(toggle);
|
|
88
|
+
this.details.oscillationState = Number(toggle);
|
|
89
|
+
return true;
|
|
234
90
|
}
|
|
235
91
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
*/
|
|
239
|
-
get oscillationState(): boolean {
|
|
240
|
-
return Boolean(this.details.oscillationState);
|
|
241
|
-
}
|
|
92
|
+
async setOscillationOn(): Promise<boolean> { return this.setOscillation(true); }
|
|
93
|
+
async setOscillationOff(): Promise<boolean> { return this.setOscillation(false); }
|
|
242
94
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
95
|
+
async setDisplay(enabled: boolean): Promise<boolean> {
|
|
96
|
+
if (typeof enabled !== 'boolean') return false;
|
|
97
|
+
if (!await this.request('setDisplay', { screenSwitch: Number(enabled) })) return false;
|
|
98
|
+
this.details.screenSwitch = Number(enabled);
|
|
99
|
+
this.details.screenState = Number(enabled);
|
|
100
|
+
return true;
|
|
248
101
|
}
|
|
249
102
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
103
|
+
async setMute(enabled: boolean): Promise<boolean> {
|
|
104
|
+
if (typeof enabled !== 'boolean') return false;
|
|
105
|
+
if (!await this.request('setMuteSwitch', { muteSwitch: Number(enabled) })) return false;
|
|
106
|
+
this.details.muteSwitch = Number(enabled);
|
|
107
|
+
this.details.muteState = Number(enabled);
|
|
108
|
+
return true;
|
|
255
109
|
}
|
|
256
110
|
|
|
257
|
-
/**
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
111
|
+
/** Public timer duration remains hours, like the other tsvesync devices. */
|
|
112
|
+
async setTimer(hours: number, action: string = this.deviceStatus === 'on' ? 'off' : 'on'): Promise<boolean> {
|
|
113
|
+
const seconds = Math.round(hours * 3600);
|
|
114
|
+
if (!Number.isFinite(hours) || hours <= 0 || hours > 12 || seconds < 1 || !['on', 'off'].includes(action)) return false;
|
|
115
|
+
const result = await this.request('setTimer', { action, total: seconds });
|
|
116
|
+
if (!result || !Number.isInteger(result.id)) return false;
|
|
117
|
+
this.timerId = result.id;
|
|
118
|
+
this.updateTimerState(seconds, action);
|
|
119
|
+
return true;
|
|
262
120
|
}
|
|
263
121
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
122
|
+
async getTimer(): Promise<boolean> {
|
|
123
|
+
const result = await this.request('getTimer');
|
|
124
|
+
if (!result || !('timers' in result) || (result.timers !== null && !Array.isArray(result.timers))) return false;
|
|
125
|
+
if (result.timers === null || result.timers.length === 0) {
|
|
126
|
+
this.updateTimerState(0, 'off');
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const timer = result.timers[0];
|
|
130
|
+
if (!Number.isInteger(timer.id) || !Number.isFinite(timer.remain) || timer.remain < 0 || !['on', 'off'].includes(timer.action)) return false;
|
|
131
|
+
this.timerId = timer.id;
|
|
132
|
+
this.updateTimerState(timer.remain, timer.action);
|
|
133
|
+
return true;
|
|
269
134
|
}
|
|
270
135
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
136
|
+
async clearTimer(): Promise<boolean> {
|
|
137
|
+
// Status responses include the remaining duration but not the timer id.
|
|
138
|
+
if (this.timerRemaining > 0 && this.timerId === undefined && !await this.getTimer()) return false;
|
|
139
|
+
if (this.timerId === undefined) return this.timerRemaining === 0;
|
|
140
|
+
if (!await this.request('clearTimer', { id: this.timerId })) return false;
|
|
141
|
+
this.updateTimerState(0, 'off');
|
|
142
|
+
return true;
|
|
276
143
|
}
|
|
277
144
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
145
|
+
private updateTimerState(seconds: number, action: string): void {
|
|
146
|
+
const remaining = Number.isFinite(seconds) ? Math.max(0, seconds) : 0;
|
|
147
|
+
this.details.timerRemain = remaining;
|
|
148
|
+
this._timer = remaining > 0 ? { duration: remaining, action } : null;
|
|
149
|
+
this.timerDeadline = remaining > 0 ? Date.now() + remaining * 1000 : undefined;
|
|
150
|
+
if (remaining === 0) this.timerId = undefined;
|
|
283
151
|
}
|
|
284
152
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
*/
|
|
288
|
-
get errorCode(): number {
|
|
289
|
-
return this.details.errorCode || 0;
|
|
153
|
+
get timerRemaining(): number {
|
|
154
|
+
return this.timerDeadline === undefined ? 0 : Math.max(0, Math.ceil((this.timerDeadline - Date.now()) / 1000));
|
|
290
155
|
}
|
|
291
156
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return this.setOscillation(true);
|
|
157
|
+
get mode(): string { return this.details.workMode === 'advancedSleep' ? 'sleep' : this.details.workMode ?? 'normal'; }
|
|
158
|
+
set mode(value: string) {
|
|
159
|
+
this.details.workMode = value === 'sleep' ? 'advancedSleep' : value === 'manual' ? 'normal' : value;
|
|
160
|
+
this.details.mode = this.mode;
|
|
297
161
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
162
|
+
get speed(): number { return this.details.manualSpeedLevel ?? this.details.fanSpeedLevel ?? 0; }
|
|
163
|
+
set speed(value: number) { this.details.manualSpeedLevel = value; }
|
|
164
|
+
get displayState(): boolean { return this.details.screenState === 1; }
|
|
165
|
+
get displayEnabled(): boolean { return this.details.screenSwitch === 1; }
|
|
166
|
+
get screenStatus(): 'on' | 'off' { return this.displayEnabled ? 'on' : 'off'; }
|
|
167
|
+
get oscillationState(): boolean { return this.details.oscillationState === 1; }
|
|
168
|
+
get oscillationEnabled(): boolean { return this.details.oscillationSwitch === 1; }
|
|
169
|
+
get muteState(): boolean { return this.details.muteState === 1; }
|
|
170
|
+
/** Raw API temperature is Fahrenheit times ten, retained for compatibility. */
|
|
171
|
+
get temperature(): number { return this.details.temperature ?? 0; }
|
|
172
|
+
get temperatureCelsius(): number | undefined {
|
|
173
|
+
const raw = this.details.temperature;
|
|
174
|
+
return typeof raw === 'number' && Number.isFinite(raw) ? (raw / 10 - 32) * 5 / 9 : undefined;
|
|
304
175
|
}
|
|
176
|
+
get humidity(): number { return this.details.humidity ?? 0; }
|
|
177
|
+
get thermalComfort(): number { return this.details.thermalComfort ?? 0; }
|
|
178
|
+
get sleepPreference(): Record<string, any> { return this.details.sleepPreference ?? {}; }
|
|
179
|
+
get scheduleCount(): number { return this.details.scheduleCount ?? 0; }
|
|
180
|
+
get errorCode(): number { return this.details.errorCode ?? 0; }
|
|
305
181
|
|
|
306
|
-
/**
|
|
307
|
-
* Display device info
|
|
308
|
-
*/
|
|
309
182
|
override display(): void {
|
|
310
183
|
super.display();
|
|
311
184
|
const info = [
|
|
@@ -319,7 +192,7 @@ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
|
|
|
319
192
|
['Mute: ', this.muteState],
|
|
320
193
|
['Mute Switch: ', this.details.muteSwitch],
|
|
321
194
|
['Timer: ', this.timer],
|
|
322
|
-
['Temperature: ', this.
|
|
195
|
+
['Temperature: ', this.temperatureCelsius ?? 'unknown', '°C'],
|
|
323
196
|
['Humidity: ', this.humidity, '%'],
|
|
324
197
|
['Thermal Comfort: ', this.thermalComfort],
|
|
325
198
|
['Sleep Preference: ', JSON.stringify(this.sleepPreference)],
|
package/src/lib/vesyncFan.ts
CHANGED
|
@@ -243,25 +243,35 @@ export const fanConfig: FanConfig = {
|
|
|
243
243
|
'LTF-F422S-KEU': {
|
|
244
244
|
module: 'VeSyncTowerFan',
|
|
245
245
|
// No child_lock: the LTF-F422S API neither reports nor accepts it (pyvesync parity)
|
|
246
|
-
features: ['display', '
|
|
246
|
+
features: ['display', 'timer', 'fan_speeds', 'tower_modes', 'oscillation', 'temperature', 'mute'],
|
|
247
|
+
modes: ['normal', 'auto', 'sleep', 'turbo'],
|
|
247
248
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
248
249
|
},
|
|
249
250
|
'LTF-F422S-WUSR': {
|
|
250
251
|
module: 'VeSyncTowerFan',
|
|
251
252
|
// No child_lock: the LTF-F422S API neither reports nor accepts it (pyvesync parity)
|
|
252
|
-
features: ['display', '
|
|
253
|
+
features: ['display', 'timer', 'fan_speeds', 'tower_modes', 'oscillation', 'temperature', 'mute'],
|
|
254
|
+
modes: ['normal', 'auto', 'sleep', 'turbo'],
|
|
255
|
+
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
256
|
+
},
|
|
257
|
+
'LTF-F422S-WJP': {
|
|
258
|
+
module: 'VeSyncTowerFan',
|
|
259
|
+
features: ['display', 'timer', 'fan_speeds', 'tower_modes', 'oscillation', 'temperature', 'mute'],
|
|
260
|
+
modes: ['normal', 'auto', 'sleep', 'turbo'],
|
|
253
261
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
254
262
|
},
|
|
255
263
|
'LTF-F422_WJP': {
|
|
256
264
|
module: 'VeSyncTowerFan',
|
|
257
265
|
// No child_lock: the LTF-F422S API neither reports nor accepts it (pyvesync parity)
|
|
258
|
-
features: ['display', '
|
|
266
|
+
features: ['display', 'timer', 'fan_speeds', 'tower_modes', 'oscillation', 'temperature', 'mute'],
|
|
267
|
+
modes: ['normal', 'auto', 'sleep', 'turbo'],
|
|
259
268
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
260
269
|
},
|
|
261
270
|
'LTF-F422S-WUS': {
|
|
262
271
|
module: 'VeSyncTowerFan',
|
|
263
272
|
// No child_lock: the LTF-F422S API neither reports nor accepts it (pyvesync parity)
|
|
264
|
-
features: ['display', '
|
|
273
|
+
features: ['display', 'timer', 'fan_speeds', 'tower_modes', 'oscillation', 'temperature', 'mute'],
|
|
274
|
+
modes: ['normal', 'auto', 'sleep', 'turbo'],
|
|
265
275
|
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
266
276
|
},
|
|
267
277
|
|