tsvesync 1.0.11

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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/api/vesync/VeSync.yaml +33 -0
  4. package/api/vesyncbulb/ESL100.yaml +74 -0
  5. package/api/vesyncbulb/ESL100CW.yaml +123 -0
  6. package/api/vesyncbulb/ESL100MC.yaml +169 -0
  7. package/api/vesyncbulb/XYD0001.yaml +193 -0
  8. package/api/vesyncfan/Classic200S.yaml +280 -0
  9. package/api/vesyncfan/Classic300S.yaml +278 -0
  10. package/api/vesyncfan/Core200S.yaml +256 -0
  11. package/api/vesyncfan/Core300S.yaml +281 -0
  12. package/api/vesyncfan/Core400S.yaml +281 -0
  13. package/api/vesyncfan/Core600S.yaml +281 -0
  14. package/api/vesyncfan/Dual200S.yaml +278 -0
  15. package/api/vesyncfan/LAP-C201S-AUSR.yaml +256 -0
  16. package/api/vesyncfan/LAP-C202S-WUSR.yaml +256 -0
  17. package/api/vesyncfan/LAP-C301S-WAAA.yaml +281 -0
  18. package/api/vesyncfan/LAP-C301S-WJP.yaml +281 -0
  19. package/api/vesyncfan/LAP-C302S-WUSB.yaml +281 -0
  20. package/api/vesyncfan/LAP-C401S-WAAA.yaml +281 -0
  21. package/api/vesyncfan/LAP-C401S-WJP.yaml +281 -0
  22. package/api/vesyncfan/LAP-C401S-WUSR.yaml +281 -0
  23. package/api/vesyncfan/LAP-C601S-WEU.yaml +281 -0
  24. package/api/vesyncfan/LAP-C601S-WUS.yaml +281 -0
  25. package/api/vesyncfan/LAP-C601S-WUSR.yaml +281 -0
  26. package/api/vesyncfan/LAP-EL551S-AEUR.yaml +167 -0
  27. package/api/vesyncfan/LAP-EL551S-AUS.yaml +167 -0
  28. package/api/vesyncfan/LAP-EL551S-WEU.yaml +167 -0
  29. package/api/vesyncfan/LAP-EL551S-WUS.yaml +167 -0
  30. package/api/vesyncfan/LAP-V102S-AASR.yaml +167 -0
  31. package/api/vesyncfan/LAP-V102S-AUSR.yaml +167 -0
  32. package/api/vesyncfan/LAP-V102S-WEU.yaml +167 -0
  33. package/api/vesyncfan/LAP-V102S-WJP.yaml +167 -0
  34. package/api/vesyncfan/LAP-V102S-WUS.yaml +167 -0
  35. package/api/vesyncfan/LAP-V201-AUSR.yaml +167 -0
  36. package/api/vesyncfan/LAP-V201S-AASR.yaml +167 -0
  37. package/api/vesyncfan/LAP-V201S-AEUR.yaml +167 -0
  38. package/api/vesyncfan/LAP-V201S-AUSR.yaml +167 -0
  39. package/api/vesyncfan/LAP-V201S-WEU.yaml +167 -0
  40. package/api/vesyncfan/LAP-V201S-WJP.yaml +167 -0
  41. package/api/vesyncfan/LAP-V201S-WUS.yaml +167 -0
  42. package/api/vesyncfan/LEH-S601S-WUS.yaml +303 -0
  43. package/api/vesyncfan/LEH-S601S-WUSR.yaml +82 -0
  44. package/api/vesyncfan/LTF-F422S-KEU.yaml +165 -0
  45. package/api/vesyncfan/LTF-F422S-WUS.yaml +165 -0
  46. package/api/vesyncfan/LTF-F422S-WUSR.yaml +167 -0
  47. package/api/vesyncfan/LTF-F422_WJP.yaml +165 -0
  48. package/api/vesyncfan/LUH-A601S-AUSW.yaml +82 -0
  49. package/api/vesyncfan/LUH-A601S-WUSB.yaml +305 -0
  50. package/api/vesyncfan/LUH-A602S-WEU.yaml +305 -0
  51. package/api/vesyncfan/LUH-A602S-WEUR.yaml +305 -0
  52. package/api/vesyncfan/LUH-A602S-WJP.yaml +305 -0
  53. package/api/vesyncfan/LUH-A602S-WUS.yaml +305 -0
  54. package/api/vesyncfan/LUH-A602S-WUSC.yaml +305 -0
  55. package/api/vesyncfan/LUH-A602S-WUSR.yaml +305 -0
  56. package/api/vesyncfan/LUH-D301S-WEU.yaml +305 -0
  57. package/api/vesyncfan/LUH-D301S-WJP.yaml +305 -0
  58. package/api/vesyncfan/LUH-D301S-WUSR.yaml +305 -0
  59. package/api/vesyncfan/LUH-M101S-WUS.yaml +278 -0
  60. package/api/vesyncfan/LUH-O451S-WEU.yaml +278 -0
  61. package/api/vesyncfan/LUH-O451S-WUS.yaml +278 -0
  62. package/api/vesyncfan/LUH-O451S-WUSR.yaml +305 -0
  63. package/api/vesyncfan/LUH-O601S-KUS.yaml +305 -0
  64. package/api/vesyncfan/LUH-O601S-WUS.yaml +305 -0
  65. package/api/vesyncfan/LV-PUR131S.yaml +108 -0
  66. package/api/vesyncfan/LV-RH131S.yaml +108 -0
  67. package/api/vesyncoutlet/ESO15-TB.yaml +124 -0
  68. package/api/vesyncoutlet/ESW01-EU.yaml +122 -0
  69. package/api/vesyncoutlet/ESW03-USA.yaml +122 -0
  70. package/api/vesyncoutlet/ESW15-USA.yaml +156 -0
  71. package/api/vesyncoutlet/wifi-switch-1.3.yaml +60 -0
  72. package/api/vesyncswitch/ESWD16.yaml +128 -0
  73. package/api/vesyncswitch/ESWL01.yaml +56 -0
  74. package/api/vesyncswitch/ESWL03.yaml +56 -0
  75. package/dist/lib/helpers.d.ts +87 -0
  76. package/dist/lib/helpers.js +222 -0
  77. package/dist/lib/helpers.js.map +1 -0
  78. package/dist/lib/vesync.d.ts +47 -0
  79. package/dist/lib/vesync.js +309 -0
  80. package/dist/lib/vesync.js.map +1 -0
  81. package/dist/lib/vesyncBaseDevice.d.ts +68 -0
  82. package/dist/lib/vesyncBaseDevice.js +116 -0
  83. package/dist/lib/vesyncBaseDevice.js.map +1 -0
  84. package/dist/lib/vesyncBulb.d.ts +0 -0
  85. package/dist/lib/vesyncBulb.js +2 -0
  86. package/dist/lib/vesyncBulb.js.map +1 -0
  87. package/dist/lib/vesyncFan.d.ts +92 -0
  88. package/dist/lib/vesyncFan.js +265 -0
  89. package/dist/lib/vesyncFan.js.map +1 -0
  90. package/dist/lib/vesyncKitchen.d.ts +0 -0
  91. package/dist/lib/vesyncKitchen.js +2 -0
  92. package/dist/lib/vesyncKitchen.js.map +1 -0
  93. package/dist/lib/vesyncOutlet.d.ts +0 -0
  94. package/dist/lib/vesyncOutlet.js +2 -0
  95. package/dist/lib/vesyncOutlet.js.map +1 -0
  96. package/dist/lib/vesyncSwitch.d.ts +0 -0
  97. package/dist/lib/vesyncSwitch.js +2 -0
  98. package/dist/lib/vesyncSwitch.js.map +1 -0
  99. package/dist/src/lib/helpers.d.ts +88 -0
  100. package/dist/src/lib/helpers.js +244 -0
  101. package/dist/src/lib/vesync.d.ts +89 -0
  102. package/dist/src/lib/vesync.js +430 -0
  103. package/dist/src/lib/vesyncBaseDevice.d.ts +43 -0
  104. package/dist/src/lib/vesyncBaseDevice.js +87 -0
  105. package/dist/src/lib/vesyncBulb.d.ts +79 -0
  106. package/dist/src/lib/vesyncBulb.js +198 -0
  107. package/dist/src/lib/vesyncBulbImpl.d.ts +38 -0
  108. package/dist/src/lib/vesyncBulbImpl.js +161 -0
  109. package/dist/src/lib/vesyncFan.d.ts +80 -0
  110. package/dist/src/lib/vesyncFan.js +406 -0
  111. package/dist/src/lib/vesyncFanImpl.d.ts +137 -0
  112. package/dist/src/lib/vesyncFanImpl.js +643 -0
  113. package/dist/src/lib/vesyncOutlet.d.ts +53 -0
  114. package/dist/src/lib/vesyncOutlet.js +98 -0
  115. package/dist/src/lib/vesyncOutletImpl.d.ts +70 -0
  116. package/dist/src/lib/vesyncOutletImpl.js +206 -0
  117. package/dist/src/lib/vesyncSwitch.d.ts +169 -0
  118. package/dist/src/lib/vesyncSwitch.js +414 -0
  119. package/dist/src/lib/vesyncSwitchImpl.d.ts +82 -0
  120. package/dist/src/lib/vesyncSwitchImpl.js +290 -0
  121. package/dist/test/testFeatures.d.ts +4 -0
  122. package/dist/test/testFeatures.js +394 -0
  123. package/dist/test/testService.d.ts +62 -0
  124. package/dist/test/testService.js +540 -0
  125. package/dist/test/testSpecial.d.ts +5 -0
  126. package/dist/test/testSpecial.js +227 -0
  127. package/package.json +53 -0
  128. package/src/lib/helpers.ts +290 -0
  129. package/src/lib/vesync.ts +530 -0
  130. package/src/lib/vesyncBaseDevice.ts +114 -0
  131. package/src/lib/vesyncBulb.ts +252 -0
  132. package/src/lib/vesyncBulbImpl.ts +215 -0
  133. package/src/lib/vesyncFan.ts +447 -0
  134. package/src/lib/vesyncFanImpl.ts +848 -0
  135. package/src/lib/vesyncOutlet.ts +148 -0
  136. package/src/lib/vesyncOutletImpl.ts +284 -0
  137. package/src/lib/vesyncSwitch.ts +546 -0
  138. package/src/lib/vesyncSwitchImpl.ts +408 -0
@@ -0,0 +1,530 @@
1
+ /**
2
+ * VeSync API Device Library
3
+ */
4
+
5
+ import { Helpers, API_RATE_LIMIT, DEFAULT_TZ } from './helpers';
6
+ import { VeSyncBaseDevice } from './vesyncBaseDevice';
7
+ import { fanModules } from './vesyncFanImpl';
8
+ import { outletModules } from './vesyncOutletImpl';
9
+ import { switchModules } from './vesyncSwitchImpl';
10
+ import { bulbModules } from './vesyncBulbImpl';
11
+ import { VeSyncBulb } from './vesyncBulb';
12
+ import { VeSyncOutlet } from './vesyncOutlet';
13
+ import { VeSyncSwitch } from './vesyncSwitch';
14
+
15
+ const DEFAULT_ENERGY_UPDATE_INTERVAL = 21600;
16
+
17
+ // Device constructor type for concrete device classes only
18
+ type DeviceConstructor = new (config: Record<string, any>, manager: VeSync) => VeSyncBaseDevice;
19
+
20
+ // Module dictionary type
21
+ interface DeviceModules {
22
+ [key: string]: DeviceConstructor;
23
+ }
24
+
25
+ // Device categories
26
+ type DeviceCategory = 'fans' | 'outlets' | 'switches' | 'bulbs';
27
+
28
+ /**
29
+ * Create device instance based on type
30
+ */
31
+ function objectFactory(details: Record<string, any>, manager: VeSync): [string, VeSyncBaseDevice | null] {
32
+ const deviceType = details.deviceType as string;
33
+ let DeviceClass: DeviceConstructor | null = null;
34
+ let category = 'unknown';
35
+
36
+ // Map of device categories to their module dictionaries
37
+ const allModules: Record<string, DeviceModules> = {
38
+ outlets: outletModules,
39
+ fans: fanModules,
40
+ bulbs: bulbModules,
41
+ switches: switchModules
42
+ };
43
+
44
+ // First try exact match in all modules
45
+ for (const [cat, modules] of Object.entries(allModules)) {
46
+ if (deviceType in modules) {
47
+ DeviceClass = modules[deviceType];
48
+ category = cat;
49
+ console.debug(`Found exact match for ${deviceType} in ${cat} modules`);
50
+ break;
51
+ }
52
+ }
53
+
54
+ // If no exact match, try to find a base class
55
+ if (!DeviceClass) {
56
+ // Device type prefix mapping
57
+ const prefixMap: Record<string, DeviceCategory> = {
58
+ // Fans
59
+ 'Core': 'fans',
60
+ 'LAP': 'fans',
61
+ 'LTF': 'fans',
62
+ 'Classic': 'fans',
63
+ 'Dual': 'fans',
64
+ 'LUH': 'fans',
65
+ 'LEH': 'fans',
66
+ 'LV-PUR': 'fans',
67
+ 'LV-RH': 'fans',
68
+ // Outlets
69
+ 'wifi-switch': 'outlets',
70
+ 'ESW03': 'outlets',
71
+ 'ESW01': 'outlets',
72
+ 'ESW15': 'outlets',
73
+ 'ESO': 'outlets',
74
+ // Switches
75
+ 'ESWL': 'switches',
76
+ 'ESWD': 'switches',
77
+ // Bulbs
78
+ 'ESL': 'bulbs',
79
+ 'XYD': 'bulbs'
80
+ };
81
+
82
+ // Find category based on device type prefix
83
+ for (const [prefix, cat] of Object.entries(prefixMap)) {
84
+ if (deviceType.startsWith(prefix)) {
85
+ category = cat;
86
+ console.debug(`Device type ${deviceType} matched prefix ${prefix} -> category ${cat}`);
87
+
88
+ // Try to find a base class in this category's modules
89
+ const modules = allModules[cat];
90
+ for (const [moduleType, ModuleClass] of Object.entries(modules)) {
91
+ const baseType = moduleType.split('-')[0]; // e.g., ESL100 from ESL100-USA
92
+ if (deviceType.startsWith(baseType)) {
93
+ DeviceClass = ModuleClass;
94
+ console.debug(`Found base type match: ${deviceType} -> ${baseType}`);
95
+ break;
96
+ }
97
+ }
98
+ break;
99
+ }
100
+ }
101
+ }
102
+
103
+ if (DeviceClass) {
104
+ try {
105
+ // Add category to device details
106
+ details.deviceCategory = category;
107
+ const device = new DeviceClass(details, manager);
108
+ return [category, device];
109
+ } catch (error) {
110
+ console.error(`Error creating device instance for ${deviceType}:`, error);
111
+ return [category, null];
112
+ }
113
+ } else {
114
+ console.warn(`No device class found for type: ${deviceType}`);
115
+ return [category, null];
116
+ }
117
+ }
118
+
119
+ /**
120
+ * VeSync Manager Class
121
+ */
122
+ export class VeSync {
123
+ private _debug: boolean;
124
+ private _redact: boolean;
125
+ private _energyUpdateInterval: number;
126
+ private _energyCheck: boolean;
127
+ private _devList: Record<string, VeSyncBaseDevice[]>;
128
+ private _lastUpdateTs: number | null;
129
+ private _inProcess: boolean;
130
+
131
+ username: string;
132
+ password: string;
133
+ token: string | null;
134
+ accountId: string | null;
135
+ countryCode: string | null;
136
+ devices: VeSyncBaseDevice[] | null;
137
+ enabled: boolean;
138
+ updateInterval: number;
139
+ timeZone: string;
140
+
141
+ fans: VeSyncBaseDevice[];
142
+ outlets: VeSyncBaseDevice[];
143
+ switches: VeSyncBaseDevice[];
144
+ bulbs: VeSyncBaseDevice[];
145
+ scales: VeSyncBaseDevice[];
146
+
147
+ /**
148
+ * Initialize VeSync Manager
149
+ */
150
+ constructor(
151
+ username: string,
152
+ password: string,
153
+ timeZone: string = DEFAULT_TZ,
154
+ debug = false,
155
+ redact = true
156
+ ) {
157
+ this._debug = debug;
158
+ this._redact = redact;
159
+ this._energyUpdateInterval = DEFAULT_ENERGY_UPDATE_INTERVAL;
160
+ this._energyCheck = true;
161
+ this._lastUpdateTs = null;
162
+ this._inProcess = false;
163
+
164
+ this.username = username;
165
+ this.password = password;
166
+ this.token = null;
167
+ this.accountId = null;
168
+ this.countryCode = null;
169
+ this.devices = null;
170
+ this.enabled = false;
171
+ this.updateInterval = API_RATE_LIMIT;
172
+
173
+ this.fans = [];
174
+ this.outlets = [];
175
+ this.switches = [];
176
+ this.bulbs = [];
177
+ this.scales = [];
178
+
179
+ this._devList = {
180
+ fans: this.fans,
181
+ outlets: this.outlets,
182
+ switches: this.switches,
183
+ bulbs: this.bulbs
184
+ };
185
+
186
+ if (typeof timeZone === 'string' && timeZone) {
187
+ const regTest = /[^a-zA-Z/_]/;
188
+ if (regTest.test(timeZone)) {
189
+ this.timeZone = DEFAULT_TZ;
190
+ console.debug('Invalid characters in time zone - ', timeZone);
191
+ } else {
192
+ this.timeZone = timeZone;
193
+ }
194
+ } else {
195
+ this.timeZone = DEFAULT_TZ;
196
+ console.debug('Time zone is not a string');
197
+ }
198
+
199
+ if (debug) {
200
+ this.debug = true;
201
+ }
202
+
203
+ if (redact) {
204
+ this.redact = true;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Get/Set debug mode
210
+ */
211
+ get debug(): boolean {
212
+ return this._debug;
213
+ }
214
+
215
+ set debug(flag: boolean) {
216
+ this._debug = flag;
217
+ }
218
+
219
+ /**
220
+ * Get/Set redact mode
221
+ */
222
+ get redact(): boolean {
223
+ return this._redact;
224
+ }
225
+
226
+ set redact(flag: boolean) {
227
+ this._redact = flag;
228
+ Helpers.shouldRedact = flag;
229
+ }
230
+
231
+ /**
232
+ * Get/Set energy update interval
233
+ */
234
+ get energyUpdateInterval(): number {
235
+ return this._energyUpdateInterval;
236
+ }
237
+
238
+ set energyUpdateInterval(interval: number) {
239
+ if (interval > 0) {
240
+ this._energyUpdateInterval = interval;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Test if device should be removed
246
+ */
247
+ static removeDevTest(device: VeSyncBaseDevice, newList: any[]): boolean {
248
+ if (Array.isArray(newList) && device.cid) {
249
+ for (const item of newList) {
250
+ if ('cid' in item && device.cid === item.cid) {
251
+ return true;
252
+ }
253
+ }
254
+ console.debug(`Device removed - ${device.deviceName} - ${device.deviceType}`);
255
+ return false;
256
+ }
257
+ return true;
258
+ }
259
+
260
+ /**
261
+ * Test if new device should be added
262
+ */
263
+ addDevTest(newDev: Record<string, any>): boolean {
264
+ if ('cid' in newDev) {
265
+ for (const devices of Object.values(this._devList)) {
266
+ for (const dev of devices) {
267
+ if (dev.cid === newDev.cid) {
268
+ return false;
269
+ }
270
+ }
271
+ }
272
+ }
273
+ return true;
274
+ }
275
+
276
+ /**
277
+ * Remove devices not found in device list return
278
+ */
279
+ removeOldDevices(devices: any[]): boolean {
280
+ for (const [key, deviceList] of Object.entries(this._devList)) {
281
+ const before = deviceList.length;
282
+ this._devList[key] = deviceList.filter(device => VeSync.removeDevTest(device, devices));
283
+ const after = this._devList[key].length;
284
+ if (before !== after) {
285
+ console.debug(`${before - after} ${key} removed`);
286
+ }
287
+ }
288
+ return true;
289
+ }
290
+
291
+ /**
292
+ * Correct devices without cid or uuid
293
+ */
294
+ static setDevId(devices: any[]): any[] {
295
+ const devRem: number[] = [];
296
+ devices.forEach((dev, index) => {
297
+ if (!dev.cid) {
298
+ if (dev.macID) {
299
+ dev.cid = dev.macID;
300
+ } else if (dev.uuid) {
301
+ dev.cid = dev.uuid;
302
+ } else {
303
+ devRem.push(index);
304
+ console.warn(`Device with no ID - ${dev.deviceName || ''}`);
305
+ }
306
+ }
307
+ });
308
+
309
+ if (devRem.length > 0) {
310
+ return devices.filter((_, index) => !devRem.includes(index));
311
+ }
312
+ return devices;
313
+ }
314
+
315
+ /**
316
+ * Process device list and instantiate device objects
317
+ */
318
+ processDevices(devList: any[]): boolean {
319
+ // Ensure devices have proper IDs
320
+ const devices = VeSync.setDevId(devList);
321
+
322
+ // Count current devices
323
+ let numDevices = 0;
324
+ for (const deviceList of Object.values(this._devList)) {
325
+ numDevices += deviceList.length;
326
+ }
327
+
328
+ if (!devices || devices.length === 0) {
329
+ console.warn('No devices found in api return');
330
+ return false;
331
+ }
332
+
333
+ // Initialize new device list or remove old devices
334
+ if (numDevices === 0) {
335
+ console.debug('New device list initialized');
336
+ } else {
337
+ this.removeOldDevices(devices);
338
+ }
339
+
340
+ // Filter out devices that already exist
341
+ const newDevices = devices.filter(dev => this.addDevTest(dev));
342
+
343
+ // Required keys for device instantiation
344
+ const requiredKeys = ['deviceType', 'deviceName', 'deviceStatus', 'cid'];
345
+
346
+ // Process each new device
347
+ for (const dev of newDevices) {
348
+ // Verify required device properties
349
+ if (!requiredKeys.every(key => key in dev)) {
350
+ console.debug(`Error adding device - missing required properties: ${dev.deviceName || 'Unknown'}`);
351
+ continue;
352
+ }
353
+
354
+ const devType = dev.deviceType;
355
+ try {
356
+ // Create device instance using factory
357
+ const [category, deviceObj] = objectFactory(dev, this);
358
+
359
+ // Add device to appropriate category if valid
360
+ if (deviceObj && category in this._devList) {
361
+ this._devList[category].push(deviceObj);
362
+ console.debug(`Added ${devType} device to ${category} category`);
363
+ } else if (category === 'unknown') {
364
+ console.debug(`Unknown device type: ${devType}`);
365
+ }
366
+ } catch (err) {
367
+ console.error(`Error creating device ${devType}:`, err);
368
+ continue;
369
+ }
370
+ }
371
+
372
+ return true;
373
+ }
374
+
375
+ /**
376
+ * Get list of VeSync devices
377
+ */
378
+ async getDevices(): Promise<boolean> {
379
+ if (!this.enabled) {
380
+ console.error('Not logged in to VeSync');
381
+ return false;
382
+ }
383
+
384
+ this._inProcess = true;
385
+ let procReturn = false;
386
+
387
+ try {
388
+ const [response] = await Helpers.callApi(
389
+ '/cloud/v2/deviceManaged/devices',
390
+ 'post',
391
+ Helpers.reqBody(this, 'devicelist'),
392
+ Helpers.reqHeaders(this)
393
+ );
394
+
395
+ if (response?.result?.list) {
396
+ const deviceList = response.result.list;
397
+ procReturn = this.processDevices(deviceList);
398
+
399
+ // Update the main devices array with all devices
400
+ this.devices = Object.values(this._devList).flat();
401
+
402
+ // Log device discovery results
403
+ console.log('\n=== Device Discovery Summary ===');
404
+ console.log(`Total devices processed: ${deviceList.length}`);
405
+
406
+ // Log device types found
407
+ const deviceTypes = deviceList.map((d: Record<string, any>) => d.deviceType);
408
+ console.log('\nDevice types found:', deviceTypes);
409
+
410
+ // Log devices by category with details
411
+ console.log('\nDevices by Category:');
412
+ console.log('---------------------');
413
+ for (const [category, devices] of Object.entries(this._devList)) {
414
+ if (devices.length > 0) {
415
+ console.log(`\n${category.toUpperCase()} (${devices.length} devices):`);
416
+ devices.forEach((d: VeSyncBaseDevice) => {
417
+ console.log(` • ${d.deviceName}`);
418
+ console.log(` Type: ${d.deviceType}`);
419
+ console.log(` Status: ${d.deviceStatus}`);
420
+ console.log(` ID: ${d.cid}`);
421
+ });
422
+ }
423
+ }
424
+
425
+ // Log summary statistics
426
+ console.log('\nSummary Statistics:');
427
+ console.log('-------------------');
428
+ console.log(`Total Devices: ${this.devices?.length || 0}`);
429
+ for (const [category, devices] of Object.entries(this._devList)) {
430
+ console.log(`${category}: ${devices.length} devices`);
431
+ }
432
+
433
+ console.log('\n=== End of Device Discovery ===\n');
434
+ } else {
435
+ console.error('No devices found in response');
436
+ }
437
+ } catch (error) {
438
+ console.error('Unable to get device list:', error);
439
+ }
440
+
441
+ this._inProcess = false;
442
+ return procReturn;
443
+ }
444
+
445
+ /**
446
+ * Login to VeSync server
447
+ */
448
+ async login(): Promise<boolean> {
449
+ const body = Helpers.reqBody(this, 'login');
450
+ console.log('Login request:', {
451
+ url: `${process.env.VESYNC_API_URL}/cloud/v1/user/login`,
452
+ body
453
+ });
454
+
455
+ try {
456
+ const [response, status] = await Helpers.callApi(
457
+ '/cloud/v1/user/login',
458
+ 'post',
459
+ body
460
+ );
461
+
462
+ console.log('Login response:', { status, response });
463
+
464
+ if (response?.result?.token) {
465
+ this.token = response.result.token;
466
+ this.accountId = response.result.accountID;
467
+ this.countryCode = response.result.countryCode;
468
+ this.enabled = true;
469
+ return true;
470
+ }
471
+
472
+ console.error('Unable to login with supplied credentials');
473
+ return false;
474
+ } catch (error) {
475
+ console.error('Login error:', error);
476
+ return false;
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Test if update interval has been exceeded
482
+ */
483
+ deviceTimeCheck(): boolean {
484
+ return (
485
+ this._lastUpdateTs === null ||
486
+ (Date.now() - this._lastUpdateTs) / 1000 > this.updateInterval
487
+ );
488
+ }
489
+
490
+ /**
491
+ * Update device list and details
492
+ */
493
+ async update(): Promise<void> {
494
+ if (this.deviceTimeCheck()) {
495
+ if (!this.enabled) {
496
+ console.error('Not logged in to VeSync');
497
+ return;
498
+ }
499
+
500
+ await this.getDevices();
501
+
502
+ console.debug('Start updating the device details one by one');
503
+ for (const deviceList of Object.values(this._devList)) {
504
+ for (const device of deviceList) {
505
+ try {
506
+ await device.getDetails();
507
+ } catch (error) {
508
+ console.error(`Error updating ${device.deviceName}:`, error);
509
+ }
510
+ }
511
+ }
512
+
513
+ this._lastUpdateTs = Date.now();
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Create device instance from details
519
+ */
520
+ createDevice(details: Record<string, any>): VeSyncBaseDevice | null {
521
+ const deviceType = details.deviceType;
522
+ const deviceClass = fanModules[deviceType];
523
+
524
+ if (deviceClass) {
525
+ return new deviceClass(details, this);
526
+ }
527
+
528
+ return null;
529
+ }
530
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Base class for VeSync devices
3
+ */
4
+
5
+ import { VeSync } from './vesync';
6
+
7
+ export abstract class VeSyncBaseDevice {
8
+ protected manager: VeSync;
9
+ protected config: Record<string, any> = {};
10
+
11
+ cid: string;
12
+ deviceName: string;
13
+ deviceStatus: string;
14
+ deviceType: string;
15
+ deviceRegion: string;
16
+ uuid: string;
17
+ configModule: string;
18
+ macId: string;
19
+ deviceCategory: string;
20
+ connectionStatus: string;
21
+
22
+ constructor(details: Record<string, any>, manager: VeSync) {
23
+ this.manager = manager;
24
+ this.cid = details.cid || '';
25
+ this.deviceName = details.deviceName || '';
26
+ this.deviceStatus = details.deviceStatus || 'off';
27
+ this.deviceType = details.deviceType || '';
28
+ this.deviceRegion = details.deviceRegion || '';
29
+ this.uuid = details.uuid || '';
30
+ this.configModule = details.configModule || '';
31
+ this.macId = details.macId || '';
32
+ this.deviceCategory = details.deviceCategory || '';
33
+ this.connectionStatus = details.connectionStatus || 'offline';
34
+ }
35
+
36
+ /**
37
+ * Return formatted device info to stdout
38
+ */
39
+ display(): void {
40
+ console.log(`Device Name: ${this.deviceName}`);
41
+ console.log(`Status: ${this.deviceStatus}`);
42
+ console.log(`Device Type: ${this.deviceType}`);
43
+ console.log(`Connection: ${this.connectionStatus}`);
44
+ console.log(`Version: ${this.config.current_firmware_version || 'Unknown'}`);
45
+ }
46
+
47
+ /**
48
+ * Return JSON details for device
49
+ */
50
+ displayJSON(): string {
51
+ return JSON.stringify({
52
+ 'Device Name': this.deviceName,
53
+ 'Status': this.deviceStatus,
54
+ 'Device Type': this.deviceType,
55
+ 'Connection': this.connectionStatus,
56
+ 'Version': this.config.current_firmware_version || 'Unknown'
57
+ }, null, 4);
58
+ }
59
+
60
+ /**
61
+ * Check API response for errors
62
+ */
63
+ protected checkResponse(response: [any, number], method: string): boolean {
64
+ const [data, status] = response;
65
+
66
+ // Check HTTP status
67
+ if (status !== 200) {
68
+ this.logError(method, `Invalid HTTP status: ${status}`);
69
+ return false;
70
+ }
71
+
72
+ // Check API response
73
+ if (!data) {
74
+ this.logError(method, 'No response data');
75
+ return false;
76
+ }
77
+
78
+ // Check error code - code 0 indicates success
79
+ if (data.code === 0) {
80
+ return true;
81
+ }
82
+
83
+ const msg = data.msg || 'Unknown error';
84
+ this.logError(method, `Error code ${data.code}: ${msg}`);
85
+ return false;
86
+ }
87
+
88
+ /**
89
+ * Log error with context
90
+ */
91
+ protected logError(method: string, error: any): void {
92
+ const errorMsg = error?.message || error;
93
+ console.error(`[${this.deviceName}] ${method}: ${errorMsg}`);
94
+
95
+ // Log additional error details if available
96
+ if (error?.response?.data) {
97
+ const responseData = error.response.data;
98
+ const msg = responseData.msg || responseData.message || JSON.stringify(responseData);
99
+ console.error(`Response: ${msg}`);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Get device details
105
+ */
106
+ abstract getDetails(): Promise<void>;
107
+
108
+ /**
109
+ * Update device details
110
+ */
111
+ async update(): Promise<void> {
112
+ await this.getDetails();
113
+ }
114
+ }