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.
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/api/vesync/VeSync.yaml +33 -0
- package/api/vesyncbulb/ESL100.yaml +74 -0
- package/api/vesyncbulb/ESL100CW.yaml +123 -0
- package/api/vesyncbulb/ESL100MC.yaml +169 -0
- package/api/vesyncbulb/XYD0001.yaml +193 -0
- package/api/vesyncfan/Classic200S.yaml +280 -0
- package/api/vesyncfan/Classic300S.yaml +278 -0
- package/api/vesyncfan/Core200S.yaml +256 -0
- package/api/vesyncfan/Core300S.yaml +281 -0
- package/api/vesyncfan/Core400S.yaml +281 -0
- package/api/vesyncfan/Core600S.yaml +281 -0
- package/api/vesyncfan/Dual200S.yaml +278 -0
- package/api/vesyncfan/LAP-C201S-AUSR.yaml +256 -0
- package/api/vesyncfan/LAP-C202S-WUSR.yaml +256 -0
- package/api/vesyncfan/LAP-C301S-WAAA.yaml +281 -0
- package/api/vesyncfan/LAP-C301S-WJP.yaml +281 -0
- package/api/vesyncfan/LAP-C302S-WUSB.yaml +281 -0
- package/api/vesyncfan/LAP-C401S-WAAA.yaml +281 -0
- package/api/vesyncfan/LAP-C401S-WJP.yaml +281 -0
- package/api/vesyncfan/LAP-C401S-WUSR.yaml +281 -0
- package/api/vesyncfan/LAP-C601S-WEU.yaml +281 -0
- package/api/vesyncfan/LAP-C601S-WUS.yaml +281 -0
- package/api/vesyncfan/LAP-C601S-WUSR.yaml +281 -0
- package/api/vesyncfan/LAP-EL551S-AEUR.yaml +167 -0
- package/api/vesyncfan/LAP-EL551S-AUS.yaml +167 -0
- package/api/vesyncfan/LAP-EL551S-WEU.yaml +167 -0
- package/api/vesyncfan/LAP-EL551S-WUS.yaml +167 -0
- package/api/vesyncfan/LAP-V102S-AASR.yaml +167 -0
- package/api/vesyncfan/LAP-V102S-AUSR.yaml +167 -0
- package/api/vesyncfan/LAP-V102S-WEU.yaml +167 -0
- package/api/vesyncfan/LAP-V102S-WJP.yaml +167 -0
- package/api/vesyncfan/LAP-V102S-WUS.yaml +167 -0
- package/api/vesyncfan/LAP-V201-AUSR.yaml +167 -0
- package/api/vesyncfan/LAP-V201S-AASR.yaml +167 -0
- package/api/vesyncfan/LAP-V201S-AEUR.yaml +167 -0
- package/api/vesyncfan/LAP-V201S-AUSR.yaml +167 -0
- package/api/vesyncfan/LAP-V201S-WEU.yaml +167 -0
- package/api/vesyncfan/LAP-V201S-WJP.yaml +167 -0
- package/api/vesyncfan/LAP-V201S-WUS.yaml +167 -0
- package/api/vesyncfan/LEH-S601S-WUS.yaml +303 -0
- package/api/vesyncfan/LEH-S601S-WUSR.yaml +82 -0
- package/api/vesyncfan/LTF-F422S-KEU.yaml +165 -0
- package/api/vesyncfan/LTF-F422S-WUS.yaml +165 -0
- package/api/vesyncfan/LTF-F422S-WUSR.yaml +167 -0
- package/api/vesyncfan/LTF-F422_WJP.yaml +165 -0
- package/api/vesyncfan/LUH-A601S-AUSW.yaml +82 -0
- package/api/vesyncfan/LUH-A601S-WUSB.yaml +305 -0
- package/api/vesyncfan/LUH-A602S-WEU.yaml +305 -0
- package/api/vesyncfan/LUH-A602S-WEUR.yaml +305 -0
- package/api/vesyncfan/LUH-A602S-WJP.yaml +305 -0
- package/api/vesyncfan/LUH-A602S-WUS.yaml +305 -0
- package/api/vesyncfan/LUH-A602S-WUSC.yaml +305 -0
- package/api/vesyncfan/LUH-A602S-WUSR.yaml +305 -0
- package/api/vesyncfan/LUH-D301S-WEU.yaml +305 -0
- package/api/vesyncfan/LUH-D301S-WJP.yaml +305 -0
- package/api/vesyncfan/LUH-D301S-WUSR.yaml +305 -0
- package/api/vesyncfan/LUH-M101S-WUS.yaml +278 -0
- package/api/vesyncfan/LUH-O451S-WEU.yaml +278 -0
- package/api/vesyncfan/LUH-O451S-WUS.yaml +278 -0
- package/api/vesyncfan/LUH-O451S-WUSR.yaml +305 -0
- package/api/vesyncfan/LUH-O601S-KUS.yaml +305 -0
- package/api/vesyncfan/LUH-O601S-WUS.yaml +305 -0
- package/api/vesyncfan/LV-PUR131S.yaml +108 -0
- package/api/vesyncfan/LV-RH131S.yaml +108 -0
- package/api/vesyncoutlet/ESO15-TB.yaml +124 -0
- package/api/vesyncoutlet/ESW01-EU.yaml +122 -0
- package/api/vesyncoutlet/ESW03-USA.yaml +122 -0
- package/api/vesyncoutlet/ESW15-USA.yaml +156 -0
- package/api/vesyncoutlet/wifi-switch-1.3.yaml +60 -0
- package/api/vesyncswitch/ESWD16.yaml +128 -0
- package/api/vesyncswitch/ESWL01.yaml +56 -0
- package/api/vesyncswitch/ESWL03.yaml +56 -0
- package/dist/lib/helpers.d.ts +87 -0
- package/dist/lib/helpers.js +222 -0
- package/dist/lib/helpers.js.map +1 -0
- package/dist/lib/vesync.d.ts +47 -0
- package/dist/lib/vesync.js +309 -0
- package/dist/lib/vesync.js.map +1 -0
- package/dist/lib/vesyncBaseDevice.d.ts +68 -0
- package/dist/lib/vesyncBaseDevice.js +116 -0
- package/dist/lib/vesyncBaseDevice.js.map +1 -0
- package/dist/lib/vesyncBulb.d.ts +0 -0
- package/dist/lib/vesyncBulb.js +2 -0
- package/dist/lib/vesyncBulb.js.map +1 -0
- package/dist/lib/vesyncFan.d.ts +92 -0
- package/dist/lib/vesyncFan.js +265 -0
- package/dist/lib/vesyncFan.js.map +1 -0
- package/dist/lib/vesyncKitchen.d.ts +0 -0
- package/dist/lib/vesyncKitchen.js +2 -0
- package/dist/lib/vesyncKitchen.js.map +1 -0
- package/dist/lib/vesyncOutlet.d.ts +0 -0
- package/dist/lib/vesyncOutlet.js +2 -0
- package/dist/lib/vesyncOutlet.js.map +1 -0
- package/dist/lib/vesyncSwitch.d.ts +0 -0
- package/dist/lib/vesyncSwitch.js +2 -0
- package/dist/lib/vesyncSwitch.js.map +1 -0
- package/dist/src/lib/helpers.d.ts +88 -0
- package/dist/src/lib/helpers.js +244 -0
- package/dist/src/lib/vesync.d.ts +89 -0
- package/dist/src/lib/vesync.js +430 -0
- package/dist/src/lib/vesyncBaseDevice.d.ts +43 -0
- package/dist/src/lib/vesyncBaseDevice.js +87 -0
- package/dist/src/lib/vesyncBulb.d.ts +79 -0
- package/dist/src/lib/vesyncBulb.js +198 -0
- package/dist/src/lib/vesyncBulbImpl.d.ts +38 -0
- package/dist/src/lib/vesyncBulbImpl.js +161 -0
- package/dist/src/lib/vesyncFan.d.ts +80 -0
- package/dist/src/lib/vesyncFan.js +406 -0
- package/dist/src/lib/vesyncFanImpl.d.ts +137 -0
- package/dist/src/lib/vesyncFanImpl.js +643 -0
- package/dist/src/lib/vesyncOutlet.d.ts +53 -0
- package/dist/src/lib/vesyncOutlet.js +98 -0
- package/dist/src/lib/vesyncOutletImpl.d.ts +70 -0
- package/dist/src/lib/vesyncOutletImpl.js +206 -0
- package/dist/src/lib/vesyncSwitch.d.ts +169 -0
- package/dist/src/lib/vesyncSwitch.js +414 -0
- package/dist/src/lib/vesyncSwitchImpl.d.ts +82 -0
- package/dist/src/lib/vesyncSwitchImpl.js +290 -0
- package/dist/test/testFeatures.d.ts +4 -0
- package/dist/test/testFeatures.js +394 -0
- package/dist/test/testService.d.ts +62 -0
- package/dist/test/testService.js +540 -0
- package/dist/test/testSpecial.d.ts +5 -0
- package/dist/test/testSpecial.js +227 -0
- package/package.json +53 -0
- package/src/lib/helpers.ts +290 -0
- package/src/lib/vesync.ts +530 -0
- package/src/lib/vesyncBaseDevice.ts +114 -0
- package/src/lib/vesyncBulb.ts +252 -0
- package/src/lib/vesyncBulbImpl.ts +215 -0
- package/src/lib/vesyncFan.ts +447 -0
- package/src/lib/vesyncFanImpl.ts +848 -0
- package/src/lib/vesyncOutlet.ts +148 -0
- package/src/lib/vesyncOutletImpl.ts +284 -0
- package/src/lib/vesyncSwitch.ts +546 -0
- 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
|
+
}
|