tsvesync 1.3.8 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/fans/airBaseV2.d.ts +8 -10
- package/dist/lib/fans/airBaseV2.js +63 -67
- package/dist/lib/vesyncBulb.d.ts +23 -6
- package/dist/lib/vesyncBulb.js +144 -32
- package/dist/lib/vesyncBulbImpl.d.ts +16 -1
- package/dist/lib/vesyncBulbImpl.js +296 -47
- package/package.json +1 -1
- package/src/lib/fans/airBaseV2.ts +65 -71
- package/src/lib/vesyncBulb.ts +160 -34
- package/src/lib/vesyncBulbImpl.ts +404 -52
|
@@ -30,10 +30,9 @@ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
30
30
|
action: string;
|
|
31
31
|
} | null);
|
|
32
32
|
/**
|
|
33
|
-
* Check response for
|
|
34
|
-
* These devices return success at API level but may have inner errors
|
|
33
|
+
* Check response for bypassV2 purifiers that surface nested result codes.
|
|
35
34
|
*/
|
|
36
|
-
protected
|
|
35
|
+
protected checkV2Response(response: any, status: number, method: string): boolean;
|
|
37
36
|
/**
|
|
38
37
|
* Get device details
|
|
39
38
|
*/
|
|
@@ -43,24 +42,23 @@ export declare class VeSyncAirBaseV2 extends VeSyncAirBypass {
|
|
|
43
42
|
*/
|
|
44
43
|
protected buildPurifierDict(devDict: Record<string, any>): void;
|
|
45
44
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @returns true if this is a LAP-V102S or LAP-V201S device
|
|
45
|
+
* Determine whether this purifier requires the powerSwitch payload and nested response handling.
|
|
48
46
|
*/
|
|
49
|
-
private
|
|
47
|
+
private requiresPowerSwitchPayload;
|
|
50
48
|
/**
|
|
51
|
-
* Override turn on
|
|
49
|
+
* Override turn on to use the powerSwitch payload required by bypassV2 purifiers.
|
|
52
50
|
*/
|
|
53
51
|
turnOn(): Promise<boolean>;
|
|
54
52
|
/**
|
|
55
|
-
* Override turn off
|
|
53
|
+
* Override turn off to use the powerSwitch payload required by bypassV2 purifiers.
|
|
56
54
|
*/
|
|
57
55
|
turnOff(): Promise<boolean>;
|
|
58
56
|
/**
|
|
59
|
-
* Override setMode
|
|
57
|
+
* Override setMode to send workMode payloads for bypassV2 purifiers.
|
|
60
58
|
*/
|
|
61
59
|
setMode(mode: string): Promise<boolean>;
|
|
62
60
|
/**
|
|
63
|
-
* Override changeFanSpeed
|
|
61
|
+
* Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
|
|
64
62
|
*/
|
|
65
63
|
changeFanSpeed(speed: number): Promise<boolean>;
|
|
66
64
|
/**
|
|
@@ -46,26 +46,21 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
46
46
|
this._timer = value;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Check response for
|
|
50
|
-
* These devices return success at API level but may have inner errors
|
|
49
|
+
* Check response for bypassV2 purifiers that surface nested result codes.
|
|
51
50
|
*/
|
|
52
|
-
|
|
53
|
-
// First check basic response
|
|
51
|
+
checkV2Response(response, status, method) {
|
|
52
|
+
// First check basic response structure
|
|
54
53
|
if (!this.checkResponse([response, status], method)) {
|
|
55
54
|
return false;
|
|
56
55
|
}
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return true; // Not a Vital series device, standard check is enough
|
|
56
|
+
// Only apply extended handling to bypassV2 purifiers (Vital/Everest)
|
|
57
|
+
if (!this.requiresPowerSwitchPayload()) {
|
|
58
|
+
return true;
|
|
61
59
|
}
|
|
62
|
-
//
|
|
60
|
+
// These devices frequently succeed with a nested non-zero code
|
|
63
61
|
if (response.result && response.result.code !== undefined && response.result.code !== 0) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
logger_1.logger.debug(`Vital series device ${this.deviceName} returned inner code ${response.result.code} for ${method}`);
|
|
67
|
-
logger_1.logger.debug('This is normal for Vital series devices - proceeding with operation');
|
|
68
|
-
// Keep the device online despite inner error
|
|
62
|
+
logger_1.logger.debug(`${this.deviceName} returned inner code ${response.result.code} for ${method}`);
|
|
63
|
+
logger_1.logger.debug('BypassV2 purifier reports non-zero inner code but request still succeeded');
|
|
69
64
|
this.connectionStatus = 'online';
|
|
70
65
|
return true;
|
|
71
66
|
}
|
|
@@ -79,22 +74,20 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
79
74
|
logger_1.logger.debug(`Getting details for device: ${this.deviceName}`);
|
|
80
75
|
const [head, body] = this.buildApiDict('getPurifierStatus');
|
|
81
76
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
82
|
-
|
|
83
|
-
const isVitalSeries = this.deviceType.includes('LAP-V102S') || this.deviceType.includes('LAP-V201S');
|
|
77
|
+
const needsV2Handling = this.requiresPowerSwitchPayload();
|
|
84
78
|
// First check the outer response
|
|
85
79
|
if (!response || status !== 200) {
|
|
86
80
|
logger_1.logger.debug(`Error getting purifier details for ${this.deviceName}: Invalid response`);
|
|
87
81
|
this.connectionStatus = 'offline';
|
|
88
82
|
return false;
|
|
89
83
|
}
|
|
90
|
-
// For
|
|
91
|
-
|
|
92
|
-
if (isVitalSeries && response.result) {
|
|
84
|
+
// For bypassV2 purifier families the API nests the result and may surface non-zero codes
|
|
85
|
+
if (needsV2Handling && response.result) {
|
|
93
86
|
// Check if we have a valid getPurifierStatus response
|
|
94
87
|
if (response.code === 0 && ((_a = response.result) === null || _a === void 0 ? void 0 : _a.result)) {
|
|
95
88
|
const statusResponse = response.result.result;
|
|
96
89
|
// If we have valid status data, use it regardless of inner result code
|
|
97
|
-
logger_1.logger.debug(`Using status data for
|
|
90
|
+
logger_1.logger.debug(`Using nested status data for bypassV2 purifier: ${this.deviceName}`);
|
|
98
91
|
this.buildPurifierDict(statusResponse);
|
|
99
92
|
if (statusResponse.configuration) {
|
|
100
93
|
this.buildConfigDict(statusResponse.configuration);
|
|
@@ -104,7 +97,7 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
104
97
|
return true;
|
|
105
98
|
}
|
|
106
99
|
}
|
|
107
|
-
// Standard processing for non-
|
|
100
|
+
// Standard processing for non-bypassV2 devices or when no status data is available
|
|
108
101
|
if (!this.checkResponse([response, status], 'getDetails') || !(response === null || response === void 0 ? void 0 : response.result)) {
|
|
109
102
|
logger_1.logger.debug('Error getting purifier details');
|
|
110
103
|
this.connectionStatus = 'offline';
|
|
@@ -133,7 +126,9 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
133
126
|
this.enabled = powerSwitch;
|
|
134
127
|
this.deviceStatus = powerSwitch ? 'on' : 'off';
|
|
135
128
|
this.mode = (_b = devDict.workMode) !== null && _b !== void 0 ? _b : 'manual';
|
|
136
|
-
|
|
129
|
+
const reportedFanSpeed = (_c = devDict.fanSpeedLevel) !== null && _c !== void 0 ? _c : 0;
|
|
130
|
+
const isAutoSentinel = reportedFanSpeed === 255;
|
|
131
|
+
this.speed = isAutoSentinel ? 0 : reportedFanSpeed;
|
|
137
132
|
this.setSpeedLevel = (_d = devDict.manualSpeedLevel) !== null && _d !== void 0 ? _d : 1;
|
|
138
133
|
// Parse filter life with proper fallback handling and logging
|
|
139
134
|
let filterLife = 0;
|
|
@@ -159,7 +154,11 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
159
154
|
light_detection_switch: Boolean((_g = devDict.lightDetectionSwitch) !== null && _g !== void 0 ? _g : 0),
|
|
160
155
|
environment_light_state: Boolean((_h = devDict.environmentLightState) !== null && _h !== void 0 ? _h : 0),
|
|
161
156
|
screen_switch: Boolean((_j = devDict.screenSwitch) !== null && _j !== void 0 ? _j : 0),
|
|
162
|
-
screenStatus: Boolean((_k = devDict.screenSwitch) !== null && _k !== void 0 ? _k : 0) ? 'on' : 'off'
|
|
157
|
+
screenStatus: Boolean((_k = devDict.screenSwitch) !== null && _k !== void 0 ? _k : 0) ? 'on' : 'off',
|
|
158
|
+
manualSpeedLevel: this.setSpeedLevel,
|
|
159
|
+
mode: this.mode,
|
|
160
|
+
speed: this.speed,
|
|
161
|
+
isAutoSentinel
|
|
163
162
|
};
|
|
164
163
|
if (this.hasFeature('air_quality')) {
|
|
165
164
|
this.details.air_quality_value = (_l = devDict.PM25) !== null && _l !== void 0 ? _l : 0;
|
|
@@ -186,29 +185,28 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
186
185
|
}
|
|
187
186
|
}
|
|
188
187
|
/**
|
|
189
|
-
*
|
|
190
|
-
* @returns true if this is a LAP-V102S or LAP-V201S device
|
|
188
|
+
* Determine whether this purifier requires the powerSwitch payload and nested response handling.
|
|
191
189
|
*/
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
requiresPowerSwitchPayload() {
|
|
191
|
+
const type = (this.deviceType || '').toUpperCase();
|
|
192
|
+
return type.startsWith('LAP-V') || type.startsWith('LAP-EL');
|
|
194
193
|
}
|
|
195
194
|
/**
|
|
196
|
-
* Override turn on
|
|
195
|
+
* Override turn on to use the powerSwitch payload required by bypassV2 purifiers.
|
|
197
196
|
*/
|
|
198
197
|
async turnOn() {
|
|
199
198
|
logger_1.logger.info(`Turning on device: ${this.deviceName}`);
|
|
200
199
|
const [head, body] = this.buildApiDict('setSwitch');
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
200
|
+
// BypassV2 purifiers require powerSwitch toggles; others fall back to the base implementation
|
|
201
|
+
if (this.requiresPowerSwitchPayload()) {
|
|
202
|
+
logger_1.logger.debug(`Using bypassV2 power payload for ${this.deviceName}`);
|
|
205
203
|
body.payload.data = {
|
|
206
204
|
powerSwitch: 1,
|
|
207
205
|
switchIdx: 0
|
|
208
206
|
};
|
|
209
207
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
210
|
-
// Use
|
|
211
|
-
const success = this.
|
|
208
|
+
// Use bypassV2 response checking
|
|
209
|
+
const success = this.checkV2Response(response, status, 'turnOn');
|
|
212
210
|
if (success) {
|
|
213
211
|
this.deviceStatus = 'on';
|
|
214
212
|
// Force a details update to sync state
|
|
@@ -221,27 +219,26 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
221
219
|
return success;
|
|
222
220
|
}
|
|
223
221
|
else {
|
|
224
|
-
// Use parent implementation for non-
|
|
222
|
+
// Use parent implementation for non-bypassV2 models
|
|
225
223
|
return super.turnOn();
|
|
226
224
|
}
|
|
227
225
|
}
|
|
228
226
|
/**
|
|
229
|
-
* Override turn off
|
|
227
|
+
* Override turn off to use the powerSwitch payload required by bypassV2 purifiers.
|
|
230
228
|
*/
|
|
231
229
|
async turnOff() {
|
|
232
230
|
logger_1.logger.info(`Turning off device: ${this.deviceName}`);
|
|
233
231
|
const [head, body] = this.buildApiDict('setSwitch');
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
logger_1.logger.debug(`Using Vital series specific payload for ${this.deviceName}`);
|
|
232
|
+
// BypassV2 purifiers require powerSwitch toggles; others fall back to the base implementation
|
|
233
|
+
if (this.requiresPowerSwitchPayload()) {
|
|
234
|
+
logger_1.logger.debug(`Using bypassV2 power payload for ${this.deviceName}`);
|
|
238
235
|
body.payload.data = {
|
|
239
236
|
powerSwitch: 0,
|
|
240
237
|
switchIdx: 0
|
|
241
238
|
};
|
|
242
239
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
243
|
-
// Use
|
|
244
|
-
const success = this.
|
|
240
|
+
// Use bypassV2 response checking
|
|
241
|
+
const success = this.checkV2Response(response, status, 'turnOff');
|
|
245
242
|
if (success) {
|
|
246
243
|
this.deviceStatus = 'off';
|
|
247
244
|
// Force a details update to sync state
|
|
@@ -254,12 +251,12 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
254
251
|
return success;
|
|
255
252
|
}
|
|
256
253
|
else {
|
|
257
|
-
// Use parent implementation for non-
|
|
254
|
+
// Use parent implementation for non-bypassV2 models
|
|
258
255
|
return super.turnOff();
|
|
259
256
|
}
|
|
260
257
|
}
|
|
261
258
|
/**
|
|
262
|
-
* Override setMode
|
|
259
|
+
* Override setMode to send workMode payloads for bypassV2 purifiers.
|
|
263
260
|
*/
|
|
264
261
|
async setMode(mode) {
|
|
265
262
|
if (!this.modes.includes(mode)) {
|
|
@@ -272,16 +269,15 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
272
269
|
logger_1.logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
|
|
273
270
|
return this.setMode('manual');
|
|
274
271
|
}
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
logger_1.logger.debug(`Using Vital series specific response handling for ${this.deviceName}`);
|
|
272
|
+
// BypassV2 purifiers need the workMode payload; other models use the base implementation
|
|
273
|
+
if (this.requiresPowerSwitchPayload()) {
|
|
274
|
+
logger_1.logger.debug(`Using bypassV2 mode payload for ${this.deviceName}`);
|
|
279
275
|
const [head, body] = this.buildApiDict('setPurifierMode');
|
|
280
276
|
body.payload.data = {
|
|
281
277
|
workMode: mode
|
|
282
278
|
};
|
|
283
279
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
284
|
-
const success = this.
|
|
280
|
+
const success = this.checkV2Response(response, status, 'setMode');
|
|
285
281
|
if (success) {
|
|
286
282
|
this.details.mode = mode;
|
|
287
283
|
this._mode = mode;
|
|
@@ -299,12 +295,12 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
299
295
|
}
|
|
300
296
|
}
|
|
301
297
|
else {
|
|
302
|
-
// Use parent implementation for non-
|
|
298
|
+
// Use parent implementation for non-bypassV2 models
|
|
303
299
|
return super.setMode(mode);
|
|
304
300
|
}
|
|
305
301
|
}
|
|
306
302
|
/**
|
|
307
|
-
* Override changeFanSpeed
|
|
303
|
+
* Override changeFanSpeed to use manualSpeedLevel for bypassV2 purifiers.
|
|
308
304
|
*/
|
|
309
305
|
async changeFanSpeed(speed) {
|
|
310
306
|
var _a;
|
|
@@ -315,17 +311,17 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
315
311
|
}
|
|
316
312
|
logger_1.logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
|
|
317
313
|
const [head, body] = this.buildApiDict('setLevel');
|
|
318
|
-
// Specific handling for
|
|
319
|
-
if (this.
|
|
320
|
-
logger_1.logger.debug(`Using
|
|
321
|
-
// For
|
|
314
|
+
// Specific handling for bypassV2 devices
|
|
315
|
+
if (this.requiresPowerSwitchPayload()) {
|
|
316
|
+
logger_1.logger.debug(`Using bypassV2 speed payload for ${this.deviceName}`);
|
|
317
|
+
// For these devices, use levelIdx, levelType, and manualSpeedLevel per YAML spec
|
|
322
318
|
body.payload.data = {
|
|
323
319
|
levelIdx: 0,
|
|
324
320
|
levelType: 'wind',
|
|
325
321
|
manualSpeedLevel: speed
|
|
326
322
|
};
|
|
327
323
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
328
|
-
const success = this.
|
|
324
|
+
const success = this.checkV2Response(response, status, 'changeFanSpeed');
|
|
329
325
|
if (success) {
|
|
330
326
|
this.speed = speed;
|
|
331
327
|
this.details.manualSpeedLevel = speed;
|
|
@@ -343,7 +339,7 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
343
339
|
}
|
|
344
340
|
}
|
|
345
341
|
else {
|
|
346
|
-
// For non-
|
|
342
|
+
// For non-bypassV2 models, use id, level, mode, and type
|
|
347
343
|
body.payload.data = {
|
|
348
344
|
id: 0,
|
|
349
345
|
level: speed,
|
|
@@ -378,14 +374,14 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
378
374
|
};
|
|
379
375
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
380
376
|
// Use the appropriate response checking method based on device type
|
|
381
|
-
const success = this.
|
|
382
|
-
? this.
|
|
377
|
+
const success = this.requiresPowerSwitchPayload()
|
|
378
|
+
? this.checkV2Response(response, status, 'setAutoPreference')
|
|
383
379
|
: this.checkResponse([response, status], 'setAutoPreference');
|
|
384
380
|
if (success) {
|
|
385
381
|
this.details.auto_preference = preference;
|
|
386
382
|
this.details.auto_preference_type = preference;
|
|
387
|
-
// Force a details update to sync state for
|
|
388
|
-
if (this.
|
|
383
|
+
// Force a details update to sync state for bypassV2 purifiers
|
|
384
|
+
if (this.requiresPowerSwitchPayload()) {
|
|
389
385
|
setTimeout(() => {
|
|
390
386
|
this.getDetails().catch(e => {
|
|
391
387
|
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
@@ -405,13 +401,13 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
405
401
|
};
|
|
406
402
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
407
403
|
// Use the appropriate response checking method based on device type
|
|
408
|
-
const success = this.
|
|
409
|
-
? this.
|
|
404
|
+
const success = this.requiresPowerSwitchPayload()
|
|
405
|
+
? this.checkV2Response(response, status, 'setLightDetection')
|
|
410
406
|
: this.checkResponse([response, status], 'setLightDetection');
|
|
411
407
|
if (success) {
|
|
412
408
|
this.details.light_detection_switch = enabled;
|
|
413
|
-
// Force a details update to sync state for
|
|
414
|
-
if (this.
|
|
409
|
+
// Force a details update to sync state for bypassV2 purifiers
|
|
410
|
+
if (this.requiresPowerSwitchPayload()) {
|
|
415
411
|
setTimeout(() => {
|
|
416
412
|
this.getDetails().catch(e => {
|
|
417
413
|
logger_1.logger.debug(`Background state refresh failed: ${e}`);
|
|
@@ -486,8 +482,8 @@ class VeSyncAirBaseV2 extends airBypass_1.VeSyncAirBypass {
|
|
|
486
482
|
};
|
|
487
483
|
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, head);
|
|
488
484
|
// Use the appropriate response checking method based on device type
|
|
489
|
-
const success = this.
|
|
490
|
-
? this.
|
|
485
|
+
const success = this.requiresPowerSwitchPayload()
|
|
486
|
+
? this.checkV2Response(response, status, 'setOscillation')
|
|
491
487
|
: this.checkResponse([response, status], 'setOscillation');
|
|
492
488
|
if (success) {
|
|
493
489
|
this.details.oscillationState = toggle;
|
package/dist/lib/vesyncBulb.d.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { VeSyncBaseDevice } from './vesyncBaseDevice';
|
|
5
5
|
import { VeSync } from './vesync';
|
|
6
|
+
interface RgbValues {
|
|
7
|
+
red: number;
|
|
8
|
+
green: number;
|
|
9
|
+
blue: number;
|
|
10
|
+
}
|
|
6
11
|
interface BulbConfig {
|
|
7
12
|
[key: string]: {
|
|
8
13
|
module: string;
|
|
@@ -22,12 +27,11 @@ export declare abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
22
27
|
protected colorSaturation: number;
|
|
23
28
|
protected colorMode: string;
|
|
24
29
|
protected features: string[];
|
|
25
|
-
protected rgbValues:
|
|
26
|
-
red: number;
|
|
27
|
-
green: number;
|
|
28
|
-
blue: number;
|
|
29
|
-
};
|
|
30
|
+
protected rgbValues: RgbValues;
|
|
30
31
|
constructor(details: Record<string, any>, manager: VeSync);
|
|
32
|
+
hasFeature(feature: string): boolean;
|
|
33
|
+
getColorModel(): string;
|
|
34
|
+
getBrightness(): number;
|
|
31
35
|
/**
|
|
32
36
|
* Get bulb details
|
|
33
37
|
*/
|
|
@@ -51,7 +55,6 @@ export declare abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
51
55
|
/**
|
|
52
56
|
* Get bulb brightness
|
|
53
57
|
*/
|
|
54
|
-
getBrightness(): number;
|
|
55
58
|
/**
|
|
56
59
|
* Get color temperature in Kelvin
|
|
57
60
|
*/
|
|
@@ -80,9 +83,23 @@ export declare abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
80
83
|
green: number;
|
|
81
84
|
blue: number;
|
|
82
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Set color temperature wrapper to maintain compatibility with consumers
|
|
88
|
+
*/
|
|
89
|
+
setColorTemperature(colorTemp: number): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* Set color via hue/saturation/value when supported
|
|
92
|
+
*/
|
|
93
|
+
setColor(hue: number, saturation: number, value?: number): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Process standard bypass (v1) detail responses shared by multiple bulbs
|
|
96
|
+
*/
|
|
97
|
+
protected processStandardBypassDetails(response: any, statusCode: number): boolean;
|
|
83
98
|
/**
|
|
84
99
|
* Set color temperature - Abstract method to be implemented by subclasses
|
|
85
100
|
*/
|
|
86
101
|
abstract setColorTemp(colorTemp: number): Promise<boolean>;
|
|
102
|
+
protected static clamp(value: number, min: number, max: number): number;
|
|
103
|
+
protected static hsvToRgb(h: number, s: number, v: number): RgbValues;
|
|
87
104
|
}
|
|
88
105
|
export {};
|
package/dist/lib/vesyncBulb.js
CHANGED
|
@@ -50,6 +50,16 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
|
|
|
50
50
|
blue: 0
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
+
hasFeature(feature) {
|
|
54
|
+
return this.features.includes(feature);
|
|
55
|
+
}
|
|
56
|
+
getColorModel() {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
return (_b = (_a = exports.bulbConfig[this.deviceType]) === null || _a === void 0 ? void 0 : _a.colorModel) !== null && _b !== void 0 ? _b : 'none';
|
|
59
|
+
}
|
|
60
|
+
getBrightness() {
|
|
61
|
+
return this.brightness;
|
|
62
|
+
}
|
|
53
63
|
/**
|
|
54
64
|
* Get bulb details
|
|
55
65
|
*/
|
|
@@ -75,30 +85,7 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
|
|
|
75
85
|
}
|
|
76
86
|
};
|
|
77
87
|
const [response, statusCode] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
78
|
-
|
|
79
|
-
if (success && (response === null || response === void 0 ? void 0 : response.result)) {
|
|
80
|
-
this.deviceStatus = response.result.enabled ? 'on' : 'off';
|
|
81
|
-
this.brightness = response.result.brightness || this.brightness;
|
|
82
|
-
if (this.features.includes('color_temp')) {
|
|
83
|
-
this.colorTemp = response.result.colorTemp || this.colorTemp;
|
|
84
|
-
}
|
|
85
|
-
if (this.features.includes('rgb_shift')) {
|
|
86
|
-
if (exports.bulbConfig[this.deviceType].colorModel === 'rgb') {
|
|
87
|
-
this.rgbValues = {
|
|
88
|
-
red: response.result.red || this.rgbValues.red,
|
|
89
|
-
green: response.result.green || this.rgbValues.green,
|
|
90
|
-
blue: response.result.blue || this.rgbValues.blue
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
this.colorHue = response.result.hue || this.colorHue;
|
|
95
|
-
this.colorSaturation = response.result.saturation || this.colorSaturation;
|
|
96
|
-
this.colorValue = response.result.value || this.colorValue;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
logger_1.logger.debug(`[${this.deviceName}] Successfully retrieved bulb details`);
|
|
100
|
-
}
|
|
101
|
-
return success;
|
|
88
|
+
return this.processStandardBypassDetails(response, statusCode);
|
|
102
89
|
}
|
|
103
90
|
/**
|
|
104
91
|
* Update bulb details
|
|
@@ -193,7 +180,8 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
|
|
|
193
180
|
logger_1.logger.error(`[${this.deviceName}] Dimming not supported`);
|
|
194
181
|
return false;
|
|
195
182
|
}
|
|
196
|
-
|
|
183
|
+
const brightnessLevel = VeSyncBulb.clamp(Math.round(brightness), 0, 100);
|
|
184
|
+
logger_1.logger.debug(`[${this.deviceName}] Setting brightness to ${brightnessLevel}`);
|
|
197
185
|
const isV2Device = this.deviceType === 'XYD0001';
|
|
198
186
|
const endpoint = isV2Device ? '/cloud/v2/deviceManaged/bypassV2' : '/cloud/v1/deviceManaged/bypass';
|
|
199
187
|
const body = isV2Device ? {
|
|
@@ -202,7 +190,7 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
|
|
|
202
190
|
configModule: this.configModule,
|
|
203
191
|
payload: {
|
|
204
192
|
data: {
|
|
205
|
-
brightness:
|
|
193
|
+
brightness: brightnessLevel,
|
|
206
194
|
colorMode: '',
|
|
207
195
|
colorTemp: '',
|
|
208
196
|
force: 0,
|
|
@@ -219,14 +207,14 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
|
|
|
219
207
|
configModule: this.configModule,
|
|
220
208
|
jsonCmd: {
|
|
221
209
|
light: {
|
|
222
|
-
brightness:
|
|
210
|
+
brightness: brightnessLevel
|
|
223
211
|
}
|
|
224
212
|
}
|
|
225
213
|
};
|
|
226
214
|
const [response] = await this.callApi(endpoint, 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
227
215
|
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
228
|
-
this.brightness =
|
|
229
|
-
logger_1.logger.info(`[${this.deviceName}] Successfully set brightness to ${
|
|
216
|
+
this.brightness = brightnessLevel;
|
|
217
|
+
logger_1.logger.info(`[${this.deviceName}] Successfully set brightness to ${brightnessLevel}`);
|
|
230
218
|
return true;
|
|
231
219
|
}
|
|
232
220
|
logger_1.logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
|
|
@@ -235,9 +223,6 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
|
|
|
235
223
|
/**
|
|
236
224
|
* Get bulb brightness
|
|
237
225
|
*/
|
|
238
|
-
getBrightness() {
|
|
239
|
-
return this.brightness;
|
|
240
|
-
}
|
|
241
226
|
/**
|
|
242
227
|
* Get color temperature in Kelvin
|
|
243
228
|
*/
|
|
@@ -292,5 +277,132 @@ class VeSyncBulb extends vesyncBaseDevice_1.VeSyncBaseDevice {
|
|
|
292
277
|
}
|
|
293
278
|
return this.rgbValues;
|
|
294
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Set color temperature wrapper to maintain compatibility with consumers
|
|
282
|
+
*/
|
|
283
|
+
async setColorTemperature(colorTemp) {
|
|
284
|
+
return this.setColorTemp(colorTemp);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Set color via hue/saturation/value when supported
|
|
288
|
+
*/
|
|
289
|
+
async setColor(hue, saturation, value = 100) {
|
|
290
|
+
var _a;
|
|
291
|
+
if (!this.features.includes('rgb_shift')) {
|
|
292
|
+
logger_1.logger.error(`[${this.deviceName}] Color control not supported`);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
const colorModel = (_a = exports.bulbConfig[this.deviceType]) === null || _a === void 0 ? void 0 : _a.colorModel;
|
|
296
|
+
if (colorModel === 'rgb' && typeof this.setRgb === 'function') {
|
|
297
|
+
const rgb = VeSyncBulb.hsvToRgb(hue, saturation, value);
|
|
298
|
+
return this.setRgb(rgb.red, rgb.green, rgb.blue);
|
|
299
|
+
}
|
|
300
|
+
if (typeof this.setHsv === 'function') {
|
|
301
|
+
return this.setHsv(hue, saturation, value);
|
|
302
|
+
}
|
|
303
|
+
logger_1.logger.error(`[${this.deviceName}] No compatible color handler found`);
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Process standard bypass (v1) detail responses shared by multiple bulbs
|
|
308
|
+
*/
|
|
309
|
+
processStandardBypassDetails(response, statusCode) {
|
|
310
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
311
|
+
const success = this.checkResponse([response, statusCode], 'getDetails');
|
|
312
|
+
if (success && (response === null || response === void 0 ? void 0 : response.result)) {
|
|
313
|
+
const innerResult = ((_a = response.result) === null || _a === void 0 ? void 0 : _a.result) || response.result;
|
|
314
|
+
if (innerResult) {
|
|
315
|
+
if (typeof innerResult.enabled === 'boolean') {
|
|
316
|
+
this.deviceStatus = innerResult.enabled ? 'on' : 'off';
|
|
317
|
+
}
|
|
318
|
+
else if (innerResult.action) {
|
|
319
|
+
this.deviceStatus = innerResult.action === 'on' ? 'on' : 'off';
|
|
320
|
+
}
|
|
321
|
+
if (innerResult.connectionStatus) {
|
|
322
|
+
this.connectionStatus = innerResult.connectionStatus;
|
|
323
|
+
}
|
|
324
|
+
if (this.features.includes('dimmable') && innerResult.brightness !== undefined) {
|
|
325
|
+
this.brightness = Number(innerResult.brightness);
|
|
326
|
+
}
|
|
327
|
+
if (this.features.includes('color_temp')) {
|
|
328
|
+
if (innerResult.colorTemp !== undefined) {
|
|
329
|
+
this.colorTemp = Number(innerResult.colorTemp);
|
|
330
|
+
}
|
|
331
|
+
else if (innerResult.colorTempe !== undefined) {
|
|
332
|
+
this.colorTemp = Number(innerResult.colorTempe);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (this.features.includes('rgb_shift')) {
|
|
336
|
+
if (((_b = exports.bulbConfig[this.deviceType]) === null || _b === void 0 ? void 0 : _b.colorModel) === 'rgb') {
|
|
337
|
+
this.rgbValues = {
|
|
338
|
+
red: Number((_c = innerResult.red) !== null && _c !== void 0 ? _c : this.rgbValues.red),
|
|
339
|
+
green: Number((_d = innerResult.green) !== null && _d !== void 0 ? _d : this.rgbValues.green),
|
|
340
|
+
blue: Number((_e = innerResult.blue) !== null && _e !== void 0 ? _e : this.rgbValues.blue)
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.colorHue = Number((_f = innerResult.hue) !== null && _f !== void 0 ? _f : this.colorHue);
|
|
345
|
+
this.colorSaturation = Number((_g = innerResult.saturation) !== null && _g !== void 0 ? _g : this.colorSaturation);
|
|
346
|
+
this.colorValue = Number((_h = innerResult.value) !== null && _h !== void 0 ? _h : this.colorValue);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
logger_1.logger.debug(`[${this.deviceName}] Successfully retrieved bulb details`);
|
|
351
|
+
}
|
|
352
|
+
return success;
|
|
353
|
+
}
|
|
354
|
+
static clamp(value, min, max) {
|
|
355
|
+
return Math.min(Math.max(value, min), max);
|
|
356
|
+
}
|
|
357
|
+
static hsvToRgb(h, s, v) {
|
|
358
|
+
const saturation = VeSyncBulb.clamp(s, 0, 100) / 100;
|
|
359
|
+
const value = VeSyncBulb.clamp(v, 0, 100) / 100;
|
|
360
|
+
const hue = ((h % 360) + 360) % 360 / 60;
|
|
361
|
+
const i = Math.floor(hue);
|
|
362
|
+
const f = hue - i;
|
|
363
|
+
const p = value * (1 - saturation);
|
|
364
|
+
const q = value * (1 - saturation * f);
|
|
365
|
+
const t = value * (1 - saturation * (1 - f));
|
|
366
|
+
let r = 0;
|
|
367
|
+
let g = 0;
|
|
368
|
+
let b = 0;
|
|
369
|
+
switch (i) {
|
|
370
|
+
case 0:
|
|
371
|
+
r = value;
|
|
372
|
+
g = t;
|
|
373
|
+
b = p;
|
|
374
|
+
break;
|
|
375
|
+
case 1:
|
|
376
|
+
r = q;
|
|
377
|
+
g = value;
|
|
378
|
+
b = p;
|
|
379
|
+
break;
|
|
380
|
+
case 2:
|
|
381
|
+
r = p;
|
|
382
|
+
g = value;
|
|
383
|
+
b = t;
|
|
384
|
+
break;
|
|
385
|
+
case 3:
|
|
386
|
+
r = p;
|
|
387
|
+
g = q;
|
|
388
|
+
b = value;
|
|
389
|
+
break;
|
|
390
|
+
case 4:
|
|
391
|
+
r = t;
|
|
392
|
+
g = p;
|
|
393
|
+
b = value;
|
|
394
|
+
break;
|
|
395
|
+
default:
|
|
396
|
+
r = value;
|
|
397
|
+
g = p;
|
|
398
|
+
b = q;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
red: Math.round(r * 255),
|
|
403
|
+
green: Math.round(g * 255),
|
|
404
|
+
blue: Math.round(b * 255)
|
|
405
|
+
};
|
|
406
|
+
}
|
|
295
407
|
}
|
|
296
408
|
exports.VeSyncBulb = VeSyncBulb;
|