tsvesync 1.0.37 → 1.0.39

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.
@@ -40,7 +40,7 @@ export declare class Helpers {
40
40
  /**
41
41
  * Calculate MD5 hash
42
42
  */
43
- static md5(text: string): string;
43
+ static hashPassword(text: string): string;
44
44
  /**
45
45
  * Build header for legacy api GET requests
46
46
  */
@@ -84,5 +84,5 @@ export declare class Helpers {
84
84
  /**
85
85
  * Calculate MD5 hash
86
86
  */
87
- static hashPassword(text: string): string;
87
+ static md5(text: string): string;
88
88
  }
@@ -34,7 +34,7 @@ class Helpers {
34
34
  /**
35
35
  * Calculate MD5 hash
36
36
  */
37
- static md5(text) {
37
+ static hashPassword(text) {
38
38
  return crypto_1.default.createHash('md5').update(text).digest('hex');
39
39
  }
40
40
  /**
@@ -250,7 +250,7 @@ class Helpers {
250
250
  /**
251
251
  * Calculate MD5 hash
252
252
  */
253
- static hashPassword(text) {
253
+ static md5(text) {
254
254
  return crypto_1.default.createHash('md5').update(text).digest('hex');
255
255
  }
256
256
  }
@@ -5,7 +5,7 @@ import { VeSyncBaseDevice } from './vesyncBaseDevice';
5
5
  import { VeSync } from './vesync';
6
6
  interface FanConfig {
7
7
  [key: string]: {
8
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier';
8
+ module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan';
9
9
  features: string[];
10
10
  };
11
11
  }
@@ -136,20 +136,20 @@ exports.fanConfig = {
136
136
  },
137
137
  // LTF Series
138
138
  'LTF-F422S-KEU': {
139
- module: 'VeSyncAirBypass',
140
- features: ['display', 'child_lock', 'night_light', 'timer']
139
+ module: 'VeSyncTowerFan',
140
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
141
141
  },
142
142
  'LTF-F422S-WUSR': {
143
- module: 'VeSyncAirBypass',
144
- features: ['display', 'child_lock', 'night_light', 'timer']
143
+ module: 'VeSyncTowerFan',
144
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
145
145
  },
146
146
  'LTF-F422_WJP': {
147
- module: 'VeSyncAirBypass',
148
- features: ['display', 'child_lock', 'night_light', 'timer']
147
+ module: 'VeSyncTowerFan',
148
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
149
149
  },
150
150
  'LTF-F422S-WUS': {
151
- module: 'VeSyncAirBypass',
152
- features: ['display', 'child_lock', 'night_light', 'timer']
151
+ module: 'VeSyncTowerFan',
152
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
153
153
  },
154
154
  // Classic Series
155
155
  'Classic300S': {
@@ -209,4 +209,136 @@ export declare class VeSyncWarmHumidifier extends VeSyncHumidifier {
209
209
  */
210
210
  get dryingModeSecondsRemaining(): number;
211
211
  }
212
+ /**
213
+ * VeSync Air Purifier with Bypass V2
214
+ */
215
+ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
216
+ protected _lightDetection: boolean;
217
+ protected _lightDetectionState: boolean;
218
+ constructor(details: Record<string, any>, manager: VeSync);
219
+ /**
220
+ * Build API dictionary
221
+ */
222
+ protected buildApiDict(method: string): [Record<string, any>, Record<string, any>];
223
+ get lightDetection(): boolean;
224
+ set lightDetection(toggle: boolean);
225
+ get lightDetectionState(): boolean;
226
+ setLightDetection(toggle: boolean): Promise<boolean>;
227
+ setLightDetectionOn(): Promise<boolean>;
228
+ setLightDetectionOff(): Promise<boolean>;
229
+ turboMode(): Promise<boolean>;
230
+ petMode(): Promise<boolean>;
231
+ /**
232
+ * Build configuration dictionary
233
+ */
234
+ protected buildConfigDict(configDict: Record<string, any>): void;
235
+ /**
236
+ * Set mode
237
+ */
238
+ protected mode_toggle(mode: string): Promise<boolean>;
239
+ }
240
+ /**
241
+ * VeSync Tower Fan Implementation
242
+ */
243
+ export declare class VeSyncTowerFan extends VeSyncAirBaseV2 {
244
+ protected readonly towerModes: readonly ["normal", "advancedSleep", "off"];
245
+ protected readonly speeds: number[];
246
+ constructor(details: Record<string, any>, manager: VeSync);
247
+ /**
248
+ * Build API dictionary
249
+ */
250
+ protected buildApiDict(method: string): [Record<string, any>, Record<string, any>];
251
+ /**
252
+ * Get device details
253
+ */
254
+ getDetails(): Promise<Boolean>;
255
+ /**
256
+ * Toggle device power
257
+ */
258
+ toggleSwitch(toggle: boolean): Promise<boolean>;
259
+ /**
260
+ * Turn device on
261
+ */
262
+ turnOn(): Promise<boolean>;
263
+ /**
264
+ * Turn device off
265
+ */
266
+ turnOff(): Promise<boolean>;
267
+ /**
268
+ * Set tower fan mode
269
+ */
270
+ mode_toggle(mode: string): Promise<boolean>;
271
+ /**
272
+ * Change fan speed
273
+ */
274
+ changeFanSpeed(speed: number): Promise<boolean>;
275
+ /**
276
+ * Set normal mode
277
+ */
278
+ normal_mode(): Promise<boolean>;
279
+ /**
280
+ * Set manual mode - adapter to set mode to normal
281
+ */
282
+ manual_mode(): Promise<boolean>;
283
+ /**
284
+ * Set advanced sleep mode
285
+ */
286
+ advanced_sleep_mode(): Promise<boolean>;
287
+ /**
288
+ * Set sleep mode - adapter for advanced sleep mode
289
+ */
290
+ sleep_mode(): Promise<boolean>;
291
+ /**
292
+ * Override setMode to handle tower fan specific modes
293
+ */
294
+ setMode(mode: string): Promise<boolean>;
295
+ /**
296
+ * Get current speed
297
+ */
298
+ get speed(): number;
299
+ /**
300
+ * Get display status
301
+ */
302
+ get displayState(): boolean;
303
+ /**
304
+ * Get oscillation status
305
+ */
306
+ get oscillationState(): boolean;
307
+ /**
308
+ * Get mute status
309
+ */
310
+ get muteState(): boolean;
311
+ /**
312
+ * Get timer status (in seconds)
313
+ */
314
+ get timer(): number;
315
+ /**
316
+ * Get temperature (in tenths of a degree)
317
+ */
318
+ get temperature(): number;
319
+ /**
320
+ * Get humidity percentage
321
+ */
322
+ get humidity(): number;
323
+ /**
324
+ * Get thermal comfort level
325
+ */
326
+ get thermalComfort(): number;
327
+ /**
328
+ * Get sleep preferences
329
+ */
330
+ get sleepPreference(): Record<string, any>;
331
+ /**
332
+ * Get schedule count
333
+ */
334
+ get scheduleCount(): number;
335
+ /**
336
+ * Get error code
337
+ */
338
+ get errorCode(): number;
339
+ /**
340
+ * Display device info
341
+ */
342
+ display(): void;
343
+ }
212
344
  export declare const fanModules: Record<string, new (details: Record<string, any>, manager: VeSync) => VeSyncFan>;
@@ -3,7 +3,7 @@
3
3
  * VeSync Fan Implementations
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.fanModules = exports.VeSyncWarmHumidifier = exports.VeSyncHumidifier = exports.VeSyncAirBypass = void 0;
6
+ exports.fanModules = exports.VeSyncTowerFan = exports.VeSyncAirBaseV2 = exports.VeSyncWarmHumidifier = exports.VeSyncHumidifier = exports.VeSyncAirBypass = void 0;
7
7
  const vesyncFan_1 = require("./vesyncFan");
8
8
  const helpers_1 = require("./helpers");
9
9
  const logger_1 = require("./logger");
@@ -1027,6 +1027,391 @@ class VeSyncWarmHumidifier extends VeSyncHumidifier {
1027
1027
  }
1028
1028
  }
1029
1029
  exports.VeSyncWarmHumidifier = VeSyncWarmHumidifier;
1030
+ /**
1031
+ * VeSync Air Purifier with Bypass V2
1032
+ */
1033
+ class VeSyncAirBaseV2 extends VeSyncAirBypass {
1034
+ constructor(details, manager) {
1035
+ super(details, manager);
1036
+ this._lightDetection = false;
1037
+ this._lightDetectionState = false;
1038
+ logger_1.logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
1039
+ }
1040
+ /**
1041
+ * Build API dictionary
1042
+ */
1043
+ buildApiDict(method) {
1044
+ const head = helpers_1.Helpers.reqHeaderBypass();
1045
+ const body = {
1046
+ ...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
1047
+ deviceId: this.cid,
1048
+ configModule: this.configModule,
1049
+ payload: {
1050
+ method,
1051
+ source: 'APP',
1052
+ data: {}
1053
+ }
1054
+ };
1055
+ return [head, body];
1056
+ }
1057
+ get lightDetection() {
1058
+ return this._lightDetection;
1059
+ }
1060
+ set lightDetection(toggle) {
1061
+ this._lightDetection = toggle;
1062
+ }
1063
+ get lightDetectionState() {
1064
+ return this._lightDetectionState;
1065
+ }
1066
+ async setLightDetection(toggle) {
1067
+ const [head, body] = this.buildApiDict('setLightDetection');
1068
+ if (!head || !body) {
1069
+ return false;
1070
+ }
1071
+ body.payload.data = { enabled: toggle };
1072
+ const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
1073
+ const success = this.checkResponse([response, status], 'setLightDetection');
1074
+ if (success) {
1075
+ this._lightDetection = toggle;
1076
+ }
1077
+ return success;
1078
+ }
1079
+ async setLightDetectionOn() {
1080
+ return this.setLightDetection(true);
1081
+ }
1082
+ async setLightDetectionOff() {
1083
+ return this.setLightDetection(false);
1084
+ }
1085
+ async turboMode() {
1086
+ return this.mode_toggle('turbo');
1087
+ }
1088
+ async petMode() {
1089
+ return this.mode_toggle('pet');
1090
+ }
1091
+ /**
1092
+ * Build configuration dictionary
1093
+ */
1094
+ buildConfigDict(configDict) {
1095
+ if (configDict) {
1096
+ this.config = configDict;
1097
+ }
1098
+ }
1099
+ /**
1100
+ * Set mode
1101
+ */
1102
+ async mode_toggle(mode) {
1103
+ const validModes = this.modes.map(m => m.toLowerCase());
1104
+ if (!validModes.includes(mode.toLowerCase())) {
1105
+ logger_1.logger.debug(`Invalid mode used - ${mode}`);
1106
+ return false;
1107
+ }
1108
+ const [head, body] = this.buildApiDict('setPurifierMode');
1109
+ if (!head || !body) {
1110
+ return false;
1111
+ }
1112
+ body.payload.data = {
1113
+ mode: mode
1114
+ };
1115
+ const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
1116
+ const success = this.checkResponse([response, status], 'mode_toggle');
1117
+ if (success) {
1118
+ this.details.mode = mode;
1119
+ }
1120
+ else {
1121
+ logger_1.logger.debug('Error setting mode');
1122
+ }
1123
+ return success;
1124
+ }
1125
+ }
1126
+ exports.VeSyncAirBaseV2 = VeSyncAirBaseV2;
1127
+ /**
1128
+ * VeSync Tower Fan Implementation
1129
+ */
1130
+ class VeSyncTowerFan extends VeSyncAirBaseV2 {
1131
+ constructor(details, manager) {
1132
+ super(details, manager);
1133
+ this.towerModes = ['normal', 'advancedSleep', 'off'];
1134
+ this.speeds = Array.from({ length: 12 }, (_, i) => i + 1);
1135
+ logger_1.logger.debug(`Initialized VeSyncTowerFan device: ${this.deviceName}`);
1136
+ }
1137
+ /**
1138
+ * Build API dictionary
1139
+ */
1140
+ buildApiDict(method) {
1141
+ const head = helpers_1.Helpers.reqHeaderBypass();
1142
+ const body = helpers_1.Helpers.reqBody(this.manager, 'bypassV2');
1143
+ body.cid = this.cid;
1144
+ body.deviceId = this.cid;
1145
+ body.configModule = this.configModule;
1146
+ body.configModel = this.configModule;
1147
+ body.payload = {
1148
+ method: method,
1149
+ source: 'APP',
1150
+ data: {}
1151
+ };
1152
+ return [head, body];
1153
+ }
1154
+ /**
1155
+ * Get device details
1156
+ */
1157
+ async getDetails() {
1158
+ logger_1.logger.debug(`Getting details for device: ${this.deviceName}`);
1159
+ const [head, body] = this.buildApiDict('getTowerFanStatus');
1160
+ const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
1161
+ if (!this.checkResponse([response, status], 'getDetails') || !(response === null || response === void 0 ? void 0 : response.result)) {
1162
+ logger_1.logger.debug('Error getting tower fan details');
1163
+ this.connectionStatus = 'offline';
1164
+ return false;
1165
+ }
1166
+ const innerResponse = response.result;
1167
+ if (innerResponse.code !== 0 || !innerResponse.result) {
1168
+ logger_1.logger.debug('Error in inner response from tower fan');
1169
+ this.connectionStatus = 'offline';
1170
+ return false;
1171
+ }
1172
+ const deviceData = innerResponse.result;
1173
+ this.deviceStatus = deviceData.powerSwitch === 1 ? 'on' : 'off';
1174
+ this.details = {
1175
+ powerSwitch: deviceData.powerSwitch,
1176
+ workMode: deviceData.workMode,
1177
+ manualSpeedLevel: deviceData.manualSpeedLevel,
1178
+ fanSpeedLevel: deviceData.fanSpeedLevel,
1179
+ screenState: deviceData.screenState,
1180
+ screenSwitch: deviceData.screenSwitch,
1181
+ oscillationState: deviceData.oscillationState,
1182
+ oscillationSwitch: deviceData.oscillationSwitch,
1183
+ muteState: deviceData.muteState,
1184
+ muteSwitch: deviceData.muteSwitch,
1185
+ timerRemain: deviceData.timerRemain,
1186
+ temperature: deviceData.temperature || 0,
1187
+ humidity: deviceData.humidity || 0,
1188
+ thermalComfort: deviceData.thermalComfort,
1189
+ sleepPreference: deviceData.sleepPreference || {},
1190
+ scheduleCount: deviceData.scheduleCount,
1191
+ displayingType: deviceData.displayingType,
1192
+ errorCode: deviceData.errorCode
1193
+ };
1194
+ logger_1.logger.debug(`Successfully got details for device: ${this.deviceName}`);
1195
+ return true;
1196
+ }
1197
+ /**
1198
+ * Toggle device power
1199
+ */
1200
+ async toggleSwitch(toggle) {
1201
+ if (typeof toggle !== 'boolean') {
1202
+ logger_1.logger.debug('Invalid toggle value for tower fan switch');
1203
+ return false;
1204
+ }
1205
+ const [head, body] = this.buildApiDict('setSwitch');
1206
+ body.payload.data = {
1207
+ powerSwitch: toggle ? 1 : 0,
1208
+ switchIdx: 0
1209
+ };
1210
+ const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
1211
+ const success = this.checkResponse([response, status], 'toggleSwitch');
1212
+ if (success) {
1213
+ this.deviceStatus = toggle ? 'on' : 'off';
1214
+ }
1215
+ else {
1216
+ logger_1.logger.debug('Error toggling tower fan power');
1217
+ }
1218
+ return success;
1219
+ }
1220
+ /**
1221
+ * Turn device on
1222
+ */
1223
+ async turnOn() {
1224
+ logger_1.logger.debug(`Turning on device: ${this.deviceName}`);
1225
+ return this.toggleSwitch(true);
1226
+ }
1227
+ /**
1228
+ * Turn device off
1229
+ */
1230
+ async turnOff() {
1231
+ logger_1.logger.debug(`Turning off device: ${this.deviceName}`);
1232
+ return this.toggleSwitch(false);
1233
+ }
1234
+ /**
1235
+ * Set tower fan mode
1236
+ */
1237
+ async mode_toggle(mode) {
1238
+ const validModes = this.towerModes.map(m => m.toLowerCase());
1239
+ if (!validModes.includes(mode.toLowerCase())) {
1240
+ logger_1.logger.debug(`Invalid tower fan mode used - ${mode}`);
1241
+ return false;
1242
+ }
1243
+ if (mode.toLowerCase() === 'off') {
1244
+ return this.turnOff();
1245
+ }
1246
+ const [head, body] = this.buildApiDict('setTowerFanMode');
1247
+ body.deviceId = this.cid;
1248
+ body.payload.data = {
1249
+ workMode: mode
1250
+ };
1251
+ const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
1252
+ const success = this.checkResponse([response, status], 'mode_toggle');
1253
+ if (success) {
1254
+ this.details.workMode = mode;
1255
+ }
1256
+ else {
1257
+ logger_1.logger.debug('Error setting tower fan mode');
1258
+ }
1259
+ return success;
1260
+ }
1261
+ /**
1262
+ * Change fan speed
1263
+ */
1264
+ async changeFanSpeed(speed) {
1265
+ if (!this.speeds.includes(speed)) {
1266
+ logger_1.logger.debug(`Invalid speed: ${speed}. Must be one of: ${this.speeds.join(', ')}`);
1267
+ return false;
1268
+ }
1269
+ const [head, body] = this.buildApiDict('setLevel');
1270
+ body.payload.data = {
1271
+ levelIdx: 0,
1272
+ levelType: 'wind',
1273
+ manualSpeedLevel: speed
1274
+ };
1275
+ const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
1276
+ const success = this.checkResponse([response, status], 'changeFanSpeed');
1277
+ if (success) {
1278
+ this.details.manualSpeedLevel = speed;
1279
+ }
1280
+ else {
1281
+ logger_1.logger.debug('Error setting fan speed');
1282
+ }
1283
+ return success;
1284
+ }
1285
+ /**
1286
+ * Set normal mode
1287
+ */
1288
+ async normal_mode() {
1289
+ logger_1.logger.debug(`Setting normal mode for device: ${this.deviceName}`);
1290
+ return this.mode_toggle('normal');
1291
+ }
1292
+ /**
1293
+ * Set manual mode - adapter to set mode to normal
1294
+ */
1295
+ async manual_mode() {
1296
+ logger_1.logger.debug(`Setting manual mode for device: ${this.deviceName}`);
1297
+ return this.normal_mode();
1298
+ }
1299
+ /**
1300
+ * Set advanced sleep mode
1301
+ */
1302
+ async advanced_sleep_mode() {
1303
+ logger_1.logger.debug(`Setting advanced sleep mode for device: ${this.deviceName}`);
1304
+ return this.mode_toggle('advancedSleep');
1305
+ }
1306
+ /**
1307
+ * Set sleep mode - adapter for advanced sleep mode
1308
+ */
1309
+ async sleep_mode() {
1310
+ logger_1.logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
1311
+ return this.advanced_sleep_mode();
1312
+ }
1313
+ /**
1314
+ * Override setMode to handle tower fan specific modes
1315
+ */
1316
+ async setMode(mode) {
1317
+ return this.mode_toggle(mode);
1318
+ }
1319
+ /**
1320
+ * Get current speed
1321
+ */
1322
+ get speed() {
1323
+ return this.details.manualSpeedLevel || 0;
1324
+ }
1325
+ /**
1326
+ * Get display status
1327
+ */
1328
+ get displayState() {
1329
+ return Boolean(this.details.screenState);
1330
+ }
1331
+ /**
1332
+ * Get oscillation status
1333
+ */
1334
+ get oscillationState() {
1335
+ return Boolean(this.details.oscillationState);
1336
+ }
1337
+ /**
1338
+ * Get mute status
1339
+ */
1340
+ get muteState() {
1341
+ return Boolean(this.details.muteState);
1342
+ }
1343
+ /**
1344
+ * Get timer status (in seconds)
1345
+ */
1346
+ get timer() {
1347
+ return this.details.timerRemain || 0;
1348
+ }
1349
+ /**
1350
+ * Get temperature (in tenths of a degree)
1351
+ */
1352
+ get temperature() {
1353
+ return this.details.temperature || 0;
1354
+ }
1355
+ /**
1356
+ * Get humidity percentage
1357
+ */
1358
+ get humidity() {
1359
+ return this.details.humidity || 0;
1360
+ }
1361
+ /**
1362
+ * Get thermal comfort level
1363
+ */
1364
+ get thermalComfort() {
1365
+ return this.details.thermalComfort || 0;
1366
+ }
1367
+ /**
1368
+ * Get sleep preferences
1369
+ */
1370
+ get sleepPreference() {
1371
+ return this.details.sleepPreference || {};
1372
+ }
1373
+ /**
1374
+ * Get schedule count
1375
+ */
1376
+ get scheduleCount() {
1377
+ return this.details.scheduleCount || 0;
1378
+ }
1379
+ /**
1380
+ * Get error code
1381
+ */
1382
+ get errorCode() {
1383
+ return this.details.errorCode || 0;
1384
+ }
1385
+ /**
1386
+ * Display device info
1387
+ */
1388
+ display() {
1389
+ super.display();
1390
+ const info = [
1391
+ ['Mode: ', this.details.workMode || ''],
1392
+ ['Speed: ', this.speed],
1393
+ ['Fan Speed Level: ', this.details.fanSpeedLevel || 0],
1394
+ ['Display: ', this.displayState],
1395
+ ['Display Switch: ', this.details.screenSwitch],
1396
+ ['Oscillation: ', this.oscillationState],
1397
+ ['Oscillation Switch: ', this.details.oscillationSwitch],
1398
+ ['Mute: ', this.muteState],
1399
+ ['Mute Switch: ', this.details.muteSwitch],
1400
+ ['Timer: ', this.timer],
1401
+ ['Temperature: ', this.temperature / 10, '°C'],
1402
+ ['Humidity: ', this.humidity, '%'],
1403
+ ['Thermal Comfort: ', this.thermalComfort],
1404
+ ['Sleep Preference: ', JSON.stringify(this.sleepPreference)],
1405
+ ['Schedule Count: ', this.scheduleCount],
1406
+ ['Displaying Type: ', this.details.displayingType],
1407
+ ['Error Code: ', this.errorCode]
1408
+ ];
1409
+ for (const [key, value, unit = ''] of info) {
1410
+ logger_1.logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
1411
+ }
1412
+ }
1413
+ }
1414
+ exports.VeSyncTowerFan = VeSyncTowerFan;
1030
1415
  // Export fan modules dictionary
1031
1416
  exports.fanModules = {
1032
1417
  // Core Series
@@ -1063,10 +1448,10 @@ exports.fanModules = {
1063
1448
  'LAP-EL551S-WEU': VeSyncAirBypass,
1064
1449
  'LAP-EL551S-WUS': VeSyncAirBypass,
1065
1450
  // LTF Series
1066
- 'LTF-F422S-KEU': VeSyncAirBypass,
1067
- 'LTF-F422S-WUSR': VeSyncAirBypass,
1068
- 'LTF-F422_WJP': VeSyncAirBypass,
1069
- 'LTF-F422S-WUS': VeSyncAirBypass,
1451
+ 'LTF-F422S-KEU': VeSyncTowerFan,
1452
+ 'LTF-F422S-WUSR': VeSyncTowerFan,
1453
+ 'LTF-F422_WJP': VeSyncTowerFan,
1454
+ 'LTF-F422S-WUS': VeSyncTowerFan,
1070
1455
  // Classic Series
1071
1456
  'Classic300S': VeSyncHumidifier,
1072
1457
  'Classic200S': VeSyncHumidifier,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "description": "A TypeScript library for interacting with VeSync smart home devices",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -58,7 +58,7 @@ export class Helpers {
58
58
  /**
59
59
  * Calculate MD5 hash
60
60
  */
61
- static md5(text: string): string {
61
+ static hashPassword(text: string): string {
62
62
  return crypto.createHash('md5').update(text).digest('hex');
63
63
  }
64
64
 
@@ -302,7 +302,7 @@ export class Helpers {
302
302
  /**
303
303
  * Calculate MD5 hash
304
304
  */
305
- static hashPassword(text: string): string {
305
+ static md5(text: string): string {
306
306
  return crypto.createHash('md5').update(text).digest('hex');
307
307
  }
308
308
  }
@@ -8,7 +8,7 @@ import { logger } from './logger';
8
8
 
9
9
  interface FanConfig {
10
10
  [key: string]: {
11
- module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier';
11
+ module: 'VeSyncAirBypass' | 'VeSyncHumidifier' | 'VeSyncWarmHumidifier' | 'VeSyncTowerFan';
12
12
  features: string[];
13
13
  };
14
14
  }
@@ -145,20 +145,20 @@ export const fanConfig: FanConfig = {
145
145
 
146
146
  // LTF Series
147
147
  'LTF-F422S-KEU': {
148
- module: 'VeSyncAirBypass',
149
- features: ['display', 'child_lock', 'night_light', 'timer']
148
+ module: 'VeSyncTowerFan',
149
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
150
150
  },
151
151
  'LTF-F422S-WUSR': {
152
- module: 'VeSyncAirBypass',
153
- features: ['display', 'child_lock', 'night_light', 'timer']
152
+ module: 'VeSyncTowerFan',
153
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
154
154
  },
155
155
  'LTF-F422_WJP': {
156
- module: 'VeSyncAirBypass',
157
- features: ['display', 'child_lock', 'night_light', 'timer']
156
+ module: 'VeSyncTowerFan',
157
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
158
158
  },
159
159
  'LTF-F422S-WUS': {
160
- module: 'VeSyncAirBypass',
161
- features: ['display', 'child_lock', 'night_light', 'timer']
160
+ module: 'VeSyncTowerFan',
161
+ features: ['display', 'child_lock', 'night_light', 'timer', 'fan_speeds', 'tower_modes']
162
162
  },
163
163
 
164
164
  // Classic Series
@@ -1254,6 +1254,477 @@ export class VeSyncWarmHumidifier extends VeSyncHumidifier {
1254
1254
  }
1255
1255
  }
1256
1256
 
1257
+ /**
1258
+ * VeSync Air Purifier with Bypass V2
1259
+ */
1260
+ export class VeSyncAirBaseV2 extends VeSyncAirBypass {
1261
+ protected _lightDetection: boolean = false;
1262
+ protected _lightDetectionState: boolean = false;
1263
+
1264
+ constructor(details: Record<string, any>, manager: VeSync) {
1265
+ super(details, manager);
1266
+ logger.debug(`Initialized VeSyncAirBaseV2 device: ${this.deviceName}`);
1267
+ }
1268
+
1269
+ /**
1270
+ * Build API dictionary
1271
+ */
1272
+ protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
1273
+ const head = Helpers.reqHeaderBypass();
1274
+ const body = {
1275
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
1276
+ deviceId: this.cid,
1277
+ configModule: this.configModule,
1278
+ payload: {
1279
+ method,
1280
+ source: 'APP',
1281
+ data: {}
1282
+ }
1283
+ };
1284
+ return [head, body];
1285
+ }
1286
+
1287
+ get lightDetection(): boolean {
1288
+ return this._lightDetection;
1289
+ }
1290
+
1291
+ set lightDetection(toggle: boolean) {
1292
+ this._lightDetection = toggle;
1293
+ }
1294
+
1295
+ get lightDetectionState(): boolean {
1296
+ return this._lightDetectionState;
1297
+ }
1298
+
1299
+ async setLightDetection(toggle: boolean): Promise<boolean> {
1300
+ const [head, body] = this.buildApiDict('setLightDetection');
1301
+ if (!head || !body) {
1302
+ return false;
1303
+ }
1304
+
1305
+ body.payload.data = { enabled: toggle };
1306
+
1307
+ const [response, status] = await this.callApi(
1308
+ '/cloud/v2/deviceManaged/bypassV2',
1309
+ 'post',
1310
+ body,
1311
+ head
1312
+ );
1313
+
1314
+ const success = this.checkResponse([response, status], 'setLightDetection');
1315
+ if (success) {
1316
+ this._lightDetection = toggle;
1317
+ }
1318
+ return success;
1319
+ }
1320
+
1321
+ async setLightDetectionOn(): Promise<boolean> {
1322
+ return this.setLightDetection(true);
1323
+ }
1324
+
1325
+ async setLightDetectionOff(): Promise<boolean> {
1326
+ return this.setLightDetection(false);
1327
+ }
1328
+
1329
+ async turboMode(): Promise<boolean> {
1330
+ return this.mode_toggle('turbo');
1331
+ }
1332
+
1333
+ async petMode(): Promise<boolean> {
1334
+ return this.mode_toggle('pet');
1335
+ }
1336
+
1337
+ /**
1338
+ * Build configuration dictionary
1339
+ */
1340
+ protected buildConfigDict(configDict: Record<string, any>): void {
1341
+ if (configDict) {
1342
+ this.config = configDict;
1343
+ }
1344
+ }
1345
+
1346
+ /**
1347
+ * Set mode
1348
+ */
1349
+ protected async mode_toggle(mode: string): Promise<boolean> {
1350
+ const validModes = this.modes.map(m => m.toLowerCase());
1351
+ if (!validModes.includes(mode.toLowerCase())) {
1352
+ logger.debug(`Invalid mode used - ${mode}`);
1353
+ return false;
1354
+ }
1355
+
1356
+ const [head, body] = this.buildApiDict('setPurifierMode');
1357
+ if (!head || !body) {
1358
+ return false;
1359
+ }
1360
+
1361
+ body.payload.data = {
1362
+ mode: mode
1363
+ };
1364
+
1365
+ const [response, status] = await this.callApi(
1366
+ '/cloud/v2/deviceManaged/bypassV2',
1367
+ 'post',
1368
+ body,
1369
+ head
1370
+ );
1371
+
1372
+ const success = this.checkResponse([response, status], 'mode_toggle');
1373
+ if (success) {
1374
+ this.details.mode = mode;
1375
+ } else {
1376
+ logger.debug('Error setting mode');
1377
+ }
1378
+ return success;
1379
+ }
1380
+ }
1381
+
1382
+ /**
1383
+ * VeSync Tower Fan Implementation
1384
+ */
1385
+ export class VeSyncTowerFan extends VeSyncAirBaseV2 {
1386
+ protected readonly towerModes = ['normal', 'advancedSleep', 'off'] as const;
1387
+ protected readonly speeds = Array.from({length: 12}, (_, i) => i + 1);
1388
+
1389
+ constructor(details: Record<string, any>, manager: VeSync) {
1390
+ super(details, manager);
1391
+ logger.debug(`Initialized VeSyncTowerFan device: ${this.deviceName}`);
1392
+ }
1393
+
1394
+ /**
1395
+ * Build API dictionary
1396
+ */
1397
+ protected buildApiDict(method: string): [Record<string, any>, Record<string, any>] {
1398
+ const head = Helpers.reqHeaderBypass();
1399
+ const body = Helpers.reqBody(this.manager, 'bypassV2');
1400
+ body.cid = this.cid;
1401
+ body.deviceId = this.cid;
1402
+ body.configModule = this.configModule;
1403
+ body.configModel = this.configModule;
1404
+ body.payload = {
1405
+ method: method,
1406
+ source: 'APP',
1407
+ data: {}
1408
+ };
1409
+ return [head, body];
1410
+ }
1411
+
1412
+ /**
1413
+ * Get device details
1414
+ */
1415
+ async getDetails(): Promise<Boolean> {
1416
+ logger.debug(`Getting details for device: ${this.deviceName}`);
1417
+ const [head, body] = this.buildApiDict('getTowerFanStatus');
1418
+
1419
+ const [response, status] = await this.callApi(
1420
+ '/cloud/v2/deviceManaged/bypassV2',
1421
+ 'post',
1422
+ body,
1423
+ head
1424
+ );
1425
+
1426
+ if (!this.checkResponse([response, status], 'getDetails') || !response?.result) {
1427
+ logger.debug('Error getting tower fan details');
1428
+ this.connectionStatus = 'offline';
1429
+ return false;
1430
+ }
1431
+
1432
+ const innerResponse = response.result;
1433
+ if (innerResponse.code !== 0 || !innerResponse.result) {
1434
+ logger.debug('Error in inner response from tower fan');
1435
+ this.connectionStatus = 'offline';
1436
+ return false;
1437
+ }
1438
+
1439
+ const deviceData = innerResponse.result;
1440
+ this.deviceStatus = deviceData.powerSwitch === 1 ? 'on' : 'off';
1441
+ this.details = {
1442
+ powerSwitch: deviceData.powerSwitch,
1443
+ workMode: deviceData.workMode,
1444
+ manualSpeedLevel: deviceData.manualSpeedLevel,
1445
+ fanSpeedLevel: deviceData.fanSpeedLevel,
1446
+ screenState: deviceData.screenState,
1447
+ screenSwitch: deviceData.screenSwitch,
1448
+ oscillationState: deviceData.oscillationState,
1449
+ oscillationSwitch: deviceData.oscillationSwitch,
1450
+ muteState: deviceData.muteState,
1451
+ muteSwitch: deviceData.muteSwitch,
1452
+ timerRemain: deviceData.timerRemain,
1453
+ temperature: deviceData.temperature || 0,
1454
+ humidity: deviceData.humidity || 0,
1455
+ thermalComfort: deviceData.thermalComfort,
1456
+ sleepPreference: deviceData.sleepPreference || {},
1457
+ scheduleCount: deviceData.scheduleCount,
1458
+ displayingType: deviceData.displayingType,
1459
+ errorCode: deviceData.errorCode
1460
+ };
1461
+
1462
+ logger.debug(`Successfully got details for device: ${this.deviceName}`);
1463
+ return true;
1464
+ }
1465
+
1466
+ /**
1467
+ * Toggle device power
1468
+ */
1469
+ async toggleSwitch(toggle: boolean): Promise<boolean> {
1470
+ if (typeof toggle !== 'boolean') {
1471
+ logger.debug('Invalid toggle value for tower fan switch');
1472
+ return false;
1473
+ }
1474
+
1475
+ const [head, body] = this.buildApiDict('setSwitch');
1476
+ body.payload.data = {
1477
+ powerSwitch: toggle ? 1 : 0,
1478
+ switchIdx: 0
1479
+ };
1480
+
1481
+ const [response, status] = await this.callApi(
1482
+ '/cloud/v2/deviceManaged/bypassV2',
1483
+ 'post',
1484
+ body,
1485
+ head
1486
+ );
1487
+
1488
+ const success = this.checkResponse([response, status], 'toggleSwitch');
1489
+ if (success) {
1490
+ this.deviceStatus = toggle ? 'on' : 'off';
1491
+ } else {
1492
+ logger.debug('Error toggling tower fan power');
1493
+ }
1494
+ return success;
1495
+ }
1496
+
1497
+ /**
1498
+ * Turn device on
1499
+ */
1500
+ async turnOn(): Promise<boolean> {
1501
+ logger.debug(`Turning on device: ${this.deviceName}`);
1502
+ return this.toggleSwitch(true);
1503
+ }
1504
+
1505
+ /**
1506
+ * Turn device off
1507
+ */
1508
+ async turnOff(): Promise<boolean> {
1509
+ logger.debug(`Turning off device: ${this.deviceName}`);
1510
+ return this.toggleSwitch(false);
1511
+ }
1512
+
1513
+ /**
1514
+ * Set tower fan mode
1515
+ */
1516
+ async mode_toggle(mode: string): Promise<boolean> {
1517
+ const validModes = this.towerModes.map(m => m.toLowerCase());
1518
+ if (!validModes.includes(mode.toLowerCase())) {
1519
+ logger.debug(`Invalid tower fan mode used - ${mode}`);
1520
+ return false;
1521
+ }
1522
+
1523
+ if (mode.toLowerCase() === 'off') {
1524
+ return this.turnOff();
1525
+ }
1526
+
1527
+ const [head, body] = this.buildApiDict('setTowerFanMode');
1528
+ body.deviceId = this.cid;
1529
+ body.payload.data = {
1530
+ workMode: mode
1531
+ };
1532
+
1533
+ const [response, status] = await this.callApi(
1534
+ '/cloud/v2/deviceManaged/bypassV2',
1535
+ 'post',
1536
+ body,
1537
+ head
1538
+ );
1539
+
1540
+ const success = this.checkResponse([response, status], 'mode_toggle');
1541
+ if (success) {
1542
+ this.details.workMode = mode;
1543
+ } else {
1544
+ logger.debug('Error setting tower fan mode');
1545
+ }
1546
+ return success;
1547
+ }
1548
+
1549
+ /**
1550
+ * Change fan speed
1551
+ */
1552
+ async changeFanSpeed(speed: number): Promise<boolean> {
1553
+ if (!this.speeds.includes(speed)) {
1554
+ logger.debug(`Invalid speed: ${speed}. Must be one of: ${this.speeds.join(', ')}`);
1555
+ return false;
1556
+ }
1557
+
1558
+ const [head, body] = this.buildApiDict('setLevel');
1559
+ body.payload.data = {
1560
+ levelIdx: 0,
1561
+ levelType: 'wind',
1562
+ manualSpeedLevel: speed
1563
+ };
1564
+
1565
+ const [response, status] = await this.callApi(
1566
+ '/cloud/v2/deviceManaged/bypassV2',
1567
+ 'post',
1568
+ body,
1569
+ head
1570
+ );
1571
+
1572
+ const success = this.checkResponse([response, status], 'changeFanSpeed');
1573
+ if (success) {
1574
+ this.details.manualSpeedLevel = speed;
1575
+ } else {
1576
+ logger.debug('Error setting fan speed');
1577
+ }
1578
+ return success;
1579
+ }
1580
+
1581
+ /**
1582
+ * Set normal mode
1583
+ */
1584
+ async normal_mode(): Promise<boolean> {
1585
+ logger.debug(`Setting normal mode for device: ${this.deviceName}`);
1586
+ return this.mode_toggle('normal');
1587
+ }
1588
+
1589
+ /**
1590
+ * Set manual mode - adapter to set mode to normal
1591
+ */
1592
+ async manual_mode(): Promise<boolean> {
1593
+ logger.debug(`Setting manual mode for device: ${this.deviceName}`);
1594
+ return this.normal_mode();
1595
+ }
1596
+
1597
+ /**
1598
+ * Set advanced sleep mode
1599
+ */
1600
+ async advanced_sleep_mode(): Promise<boolean> {
1601
+ logger.debug(`Setting advanced sleep mode for device: ${this.deviceName}`);
1602
+ return this.mode_toggle('advancedSleep');
1603
+ }
1604
+
1605
+ /**
1606
+ * Set sleep mode - adapter for advanced sleep mode
1607
+ */
1608
+ async sleep_mode(): Promise<boolean> {
1609
+ logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
1610
+ return this.advanced_sleep_mode();
1611
+ }
1612
+
1613
+ /**
1614
+ * Override setMode to handle tower fan specific modes
1615
+ */
1616
+ async setMode(mode: string): Promise<boolean> {
1617
+ return this.mode_toggle(mode);
1618
+ }
1619
+
1620
+ /**
1621
+ * Get current speed
1622
+ */
1623
+ get speed(): number {
1624
+ return this.details.manualSpeedLevel || 0;
1625
+ }
1626
+
1627
+ /**
1628
+ * Get display status
1629
+ */
1630
+ get displayState(): boolean {
1631
+ return Boolean(this.details.screenState);
1632
+ }
1633
+
1634
+ /**
1635
+ * Get oscillation status
1636
+ */
1637
+ get oscillationState(): boolean {
1638
+ return Boolean(this.details.oscillationState);
1639
+ }
1640
+
1641
+ /**
1642
+ * Get mute status
1643
+ */
1644
+ get muteState(): boolean {
1645
+ return Boolean(this.details.muteState);
1646
+ }
1647
+
1648
+ /**
1649
+ * Get timer status (in seconds)
1650
+ */
1651
+ get timer(): number {
1652
+ return this.details.timerRemain || 0;
1653
+ }
1654
+
1655
+ /**
1656
+ * Get temperature (in tenths of a degree)
1657
+ */
1658
+ get temperature(): number {
1659
+ return this.details.temperature || 0;
1660
+ }
1661
+
1662
+ /**
1663
+ * Get humidity percentage
1664
+ */
1665
+ get humidity(): number {
1666
+ return this.details.humidity || 0;
1667
+ }
1668
+
1669
+ /**
1670
+ * Get thermal comfort level
1671
+ */
1672
+ get thermalComfort(): number {
1673
+ return this.details.thermalComfort || 0;
1674
+ }
1675
+
1676
+ /**
1677
+ * Get sleep preferences
1678
+ */
1679
+ get sleepPreference(): Record<string, any> {
1680
+ return this.details.sleepPreference || {};
1681
+ }
1682
+
1683
+ /**
1684
+ * Get schedule count
1685
+ */
1686
+ get scheduleCount(): number {
1687
+ return this.details.scheduleCount || 0;
1688
+ }
1689
+
1690
+ /**
1691
+ * Get error code
1692
+ */
1693
+ get errorCode(): number {
1694
+ return this.details.errorCode || 0;
1695
+ }
1696
+
1697
+ /**
1698
+ * Display device info
1699
+ */
1700
+ override display(): void {
1701
+ super.display();
1702
+ const info = [
1703
+ ['Mode: ', this.details.workMode || ''],
1704
+ ['Speed: ', this.speed],
1705
+ ['Fan Speed Level: ', this.details.fanSpeedLevel || 0],
1706
+ ['Display: ', this.displayState],
1707
+ ['Display Switch: ', this.details.screenSwitch],
1708
+ ['Oscillation: ', this.oscillationState],
1709
+ ['Oscillation Switch: ', this.details.oscillationSwitch],
1710
+ ['Mute: ', this.muteState],
1711
+ ['Mute Switch: ', this.details.muteSwitch],
1712
+ ['Timer: ', this.timer],
1713
+ ['Temperature: ', this.temperature / 10, '°C'],
1714
+ ['Humidity: ', this.humidity, '%'],
1715
+ ['Thermal Comfort: ', this.thermalComfort],
1716
+ ['Sleep Preference: ', JSON.stringify(this.sleepPreference)],
1717
+ ['Schedule Count: ', this.scheduleCount],
1718
+ ['Displaying Type: ', this.details.displayingType],
1719
+ ['Error Code: ', this.errorCode]
1720
+ ];
1721
+
1722
+ for (const [key, value, unit = ''] of info) {
1723
+ logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`);
1724
+ }
1725
+ }
1726
+ }
1727
+
1257
1728
  // Export fan modules dictionary
1258
1729
  export const fanModules: Record<string, new (details: Record<string, any>, manager: VeSync) => VeSyncFan> = {
1259
1730
  // Core Series
@@ -1292,10 +1763,10 @@ export const fanModules: Record<string, new (details: Record<string, any>, manag
1292
1763
  'LAP-EL551S-WUS': VeSyncAirBypass,
1293
1764
 
1294
1765
  // LTF Series
1295
- 'LTF-F422S-KEU': VeSyncAirBypass,
1296
- 'LTF-F422S-WUSR': VeSyncAirBypass,
1297
- 'LTF-F422_WJP': VeSyncAirBypass,
1298
- 'LTF-F422S-WUS': VeSyncAirBypass,
1766
+ 'LTF-F422S-KEU': VeSyncTowerFan,
1767
+ 'LTF-F422S-WUSR': VeSyncTowerFan,
1768
+ 'LTF-F422_WJP': VeSyncTowerFan,
1769
+ 'LTF-F422S-WUS': VeSyncTowerFan,
1299
1770
 
1300
1771
  // Classic Series
1301
1772
  'Classic300S': VeSyncHumidifier,