tsvesync 1.3.9 → 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/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/vesyncBulb.ts +160 -34
- package/src/lib/vesyncBulbImpl.ts +404 -52
|
@@ -14,6 +14,86 @@ class VeSyncBulbESL100 extends vesyncBulb_1.VeSyncBulb {
|
|
|
14
14
|
constructor(details, manager) {
|
|
15
15
|
super(details, manager);
|
|
16
16
|
}
|
|
17
|
+
async getDetails() {
|
|
18
|
+
logger_1.logger.debug(`[${this.deviceName}] Fetching ESL100 details`);
|
|
19
|
+
const body = {
|
|
20
|
+
...helpers_1.Helpers.reqBody(this.manager, 'devicedetail'),
|
|
21
|
+
uuid: this.uuid
|
|
22
|
+
};
|
|
23
|
+
const [response, statusCode] = await this.callApi('/SmartBulb/v1/device/devicedetail', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
24
|
+
const success = this.checkResponse([response, statusCode], 'getDetails');
|
|
25
|
+
if (success && (response === null || response === void 0 ? void 0 : response.result)) {
|
|
26
|
+
const result = response.result;
|
|
27
|
+
if (result.deviceStatus) {
|
|
28
|
+
this.deviceStatus = result.deviceStatus;
|
|
29
|
+
}
|
|
30
|
+
if (result.connectionStatus) {
|
|
31
|
+
this.connectionStatus = result.connectionStatus;
|
|
32
|
+
}
|
|
33
|
+
if (result.brightNess !== undefined) {
|
|
34
|
+
this.brightness = Number(result.brightNess);
|
|
35
|
+
}
|
|
36
|
+
else if (result.brightness !== undefined) {
|
|
37
|
+
this.brightness = Number(result.brightness);
|
|
38
|
+
}
|
|
39
|
+
logger_1.logger.debug(`[${this.deviceName}] Updated details: status=${this.deviceStatus}, brightness=${this.brightness}`);
|
|
40
|
+
}
|
|
41
|
+
return success;
|
|
42
|
+
}
|
|
43
|
+
async turnOn() {
|
|
44
|
+
const body = {
|
|
45
|
+
...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
|
|
46
|
+
uuid: this.uuid,
|
|
47
|
+
status: 'on'
|
|
48
|
+
};
|
|
49
|
+
const [response] = await this.callApi('/SmartBulb/v1/device/devicestatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
50
|
+
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
51
|
+
this.deviceStatus = 'on';
|
|
52
|
+
this.connectionStatus = 'online';
|
|
53
|
+
logger_1.logger.info(`[${this.deviceName}] Turned on successfully`);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
logger_1.logger.error(`[${this.deviceName}] Failed to turn on: ${JSON.stringify(response)}`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
async turnOff() {
|
|
60
|
+
const body = {
|
|
61
|
+
...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
|
|
62
|
+
uuid: this.uuid,
|
|
63
|
+
status: 'off'
|
|
64
|
+
};
|
|
65
|
+
const [response] = await this.callApi('/SmartBulb/v1/device/devicestatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
66
|
+
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
67
|
+
this.deviceStatus = 'off';
|
|
68
|
+
logger_1.logger.info(`[${this.deviceName}] Turned off successfully`);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
logger_1.logger.error(`[${this.deviceName}] Failed to turn off: ${JSON.stringify(response)}`);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
async setBrightness(brightness) {
|
|
75
|
+
if (!this.features.includes('dimmable')) {
|
|
76
|
+
logger_1.logger.error(`[${this.deviceName}] Dimming not supported`);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const brightnessUpdate = vesyncBulb_1.VeSyncBulb.clamp(Math.round(brightness), 0, 100);
|
|
80
|
+
const body = {
|
|
81
|
+
...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
|
|
82
|
+
uuid: this.uuid,
|
|
83
|
+
status: 'on',
|
|
84
|
+
brightNess: String(brightnessUpdate)
|
|
85
|
+
};
|
|
86
|
+
const [response] = await this.callApi('/SmartBulb/v1/device/updateBrightness', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
87
|
+
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
88
|
+
this.brightness = brightnessUpdate;
|
|
89
|
+
this.deviceStatus = 'on';
|
|
90
|
+
this.connectionStatus = 'online';
|
|
91
|
+
logger_1.logger.info(`[${this.deviceName}] Brightness set to ${brightnessUpdate}`);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
logger_1.logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
17
97
|
async setColorTemp(colorTemp) {
|
|
18
98
|
logger_1.logger.error(`[${this.deviceName}] Color temperature control not supported`);
|
|
19
99
|
return false;
|
|
@@ -27,26 +107,106 @@ class VeSyncBulbESL100CW extends vesyncBulb_1.VeSyncBulb {
|
|
|
27
107
|
constructor(details, manager) {
|
|
28
108
|
super(details, manager);
|
|
29
109
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
110
|
+
buildBypassBody(lightPayload) {
|
|
111
|
+
return {
|
|
112
|
+
...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
|
|
113
|
+
cid: this.cid,
|
|
114
|
+
configModule: this.configModule,
|
|
115
|
+
jsonCmd: {
|
|
116
|
+
light: lightPayload
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async getDetails() {
|
|
121
|
+
var _a;
|
|
122
|
+
logger_1.logger.debug(`[${this.deviceName}] Fetching ESL100CW status`);
|
|
36
123
|
const body = {
|
|
37
124
|
...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
|
|
38
125
|
cid: this.cid,
|
|
39
126
|
configModule: this.configModule,
|
|
40
127
|
jsonCmd: {
|
|
41
|
-
|
|
42
|
-
|
|
128
|
+
getLightStatus: 'get'
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const [response, statusCode] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
132
|
+
const success = this.checkResponse([response, statusCode], 'getDetails');
|
|
133
|
+
if (success) {
|
|
134
|
+
const inner = ((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.result) || (response === null || response === void 0 ? void 0 : response.result);
|
|
135
|
+
const light = inner === null || inner === void 0 ? void 0 : inner.light;
|
|
136
|
+
if (light) {
|
|
137
|
+
this.deviceStatus = light.action === 'on' ? 'on' : 'off';
|
|
138
|
+
if (light.brightness !== undefined) {
|
|
139
|
+
this.brightness = Number(light.brightness);
|
|
140
|
+
}
|
|
141
|
+
if (light.colorTempe !== undefined) {
|
|
142
|
+
this.colorTemp = Number(light.colorTempe);
|
|
43
143
|
}
|
|
44
144
|
}
|
|
145
|
+
}
|
|
146
|
+
return success;
|
|
147
|
+
}
|
|
148
|
+
async turnOn() {
|
|
149
|
+
const [response] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', this.buildBypassBody({ action: 'on' }), helpers_1.Helpers.reqHeaders(this.manager));
|
|
150
|
+
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
151
|
+
this.deviceStatus = 'on';
|
|
152
|
+
this.connectionStatus = 'online';
|
|
153
|
+
logger_1.logger.info(`[${this.deviceName}] Turned on successfully`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
logger_1.logger.error(`[${this.deviceName}] Failed to turn on: ${JSON.stringify(response)}`);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
async turnOff() {
|
|
160
|
+
const [response] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', this.buildBypassBody({ action: 'off' }), helpers_1.Helpers.reqHeaders(this.manager));
|
|
161
|
+
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
162
|
+
this.deviceStatus = 'off';
|
|
163
|
+
logger_1.logger.info(`[${this.deviceName}] Turned off successfully`);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
logger_1.logger.error(`[${this.deviceName}] Failed to turn off: ${JSON.stringify(response)}`);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
async setBrightness(brightness) {
|
|
170
|
+
if (!this.features.includes('dimmable')) {
|
|
171
|
+
logger_1.logger.error(`[${this.deviceName}] Dimming not supported`);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
const brightnessUpdate = vesyncBulb_1.VeSyncBulb.clamp(Math.round(brightness), 0, 100);
|
|
175
|
+
const light = {
|
|
176
|
+
action: 'on',
|
|
177
|
+
brightness: brightnessUpdate
|
|
45
178
|
};
|
|
46
|
-
|
|
179
|
+
if (this.features.includes('color_temp')) {
|
|
180
|
+
light.colorTempe = this.colorTemp;
|
|
181
|
+
}
|
|
182
|
+
const [response] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', this.buildBypassBody(light), helpers_1.Helpers.reqHeaders(this.manager));
|
|
47
183
|
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
48
|
-
this.
|
|
49
|
-
|
|
184
|
+
this.brightness = brightnessUpdate;
|
|
185
|
+
this.deviceStatus = 'on';
|
|
186
|
+
this.connectionStatus = 'online';
|
|
187
|
+
logger_1.logger.info(`[${this.deviceName}] Brightness set to ${brightnessUpdate}`);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
logger_1.logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
async setColorTemp(colorTemp) {
|
|
194
|
+
if (!this.features.includes('color_temp')) {
|
|
195
|
+
logger_1.logger.error(`[${this.deviceName}] Color temperature control not supported`);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const colorTempUpdate = vesyncBulb_1.VeSyncBulb.clamp(Math.round(colorTemp), 0, 100);
|
|
199
|
+
const light = {
|
|
200
|
+
action: 'on',
|
|
201
|
+
brightness: this.brightness,
|
|
202
|
+
colorTempe: colorTempUpdate
|
|
203
|
+
};
|
|
204
|
+
const [response] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', this.buildBypassBody(light), helpers_1.Helpers.reqHeaders(this.manager));
|
|
205
|
+
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
206
|
+
this.colorTemp = colorTempUpdate;
|
|
207
|
+
this.deviceStatus = 'on';
|
|
208
|
+
this.connectionStatus = 'online';
|
|
209
|
+
logger_1.logger.info(`[${this.deviceName}] Color temperature set to ${colorTempUpdate}`);
|
|
50
210
|
return true;
|
|
51
211
|
}
|
|
52
212
|
logger_1.logger.error(`[${this.deviceName}] Failed to set color temperature: ${JSON.stringify(response)}`);
|
|
@@ -167,8 +327,104 @@ class VeSyncBulbESL100MC extends vesyncBulb_1.VeSyncBulb {
|
|
|
167
327
|
constructor(details, manager) {
|
|
168
328
|
super(details, manager);
|
|
169
329
|
}
|
|
170
|
-
|
|
171
|
-
|
|
330
|
+
buildBypassV2Payload(method, data) {
|
|
331
|
+
return {
|
|
332
|
+
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
|
|
333
|
+
cid: this.cid,
|
|
334
|
+
configModule: this.configModule,
|
|
335
|
+
payload: {
|
|
336
|
+
method,
|
|
337
|
+
source: 'APP',
|
|
338
|
+
data
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
handleBypassV2Response(response, context) {
|
|
343
|
+
if (!response) {
|
|
344
|
+
logger_1.logger.error(`[${this.deviceName}] ${context} returned empty response`);
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
if (response.code !== 0) {
|
|
348
|
+
logger_1.logger.error(`[${this.deviceName}] ${context} failed: ${JSON.stringify(response)}`);
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
const inner = response.result;
|
|
352
|
+
if ((inner === null || inner === void 0 ? void 0 : inner.code) !== undefined && inner.code !== 0) {
|
|
353
|
+
logger_1.logger.error(`[${this.deviceName}] ${context} inner error: ${JSON.stringify(response)}`);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
async getDetails() {
|
|
359
|
+
var _a, _b, _c, _d;
|
|
360
|
+
logger_1.logger.debug(`[${this.deviceName}] Fetching ESL100MC status`);
|
|
361
|
+
const body = this.buildBypassV2Payload('getLightStatus', {});
|
|
362
|
+
const [response, statusCode] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
363
|
+
const success = this.checkResponse([response, statusCode], 'getDetails');
|
|
364
|
+
if (!success) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
const inner = ((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.result) || (response === null || response === void 0 ? void 0 : response.result);
|
|
368
|
+
if (inner) {
|
|
369
|
+
if (inner.action) {
|
|
370
|
+
this.deviceStatus = inner.action === 'on' ? 'on' : 'off';
|
|
371
|
+
}
|
|
372
|
+
if (inner.brightness !== undefined) {
|
|
373
|
+
this.brightness = Number(inner.brightness);
|
|
374
|
+
}
|
|
375
|
+
this.rgbValues = {
|
|
376
|
+
red: Number((_b = inner.red) !== null && _b !== void 0 ? _b : this.rgbValues.red),
|
|
377
|
+
green: Number((_c = inner.green) !== null && _c !== void 0 ? _c : this.rgbValues.green),
|
|
378
|
+
blue: Number((_d = inner.blue) !== null && _d !== void 0 ? _d : this.rgbValues.blue)
|
|
379
|
+
};
|
|
380
|
+
this.colorMode = inner.colorMode || this.colorMode;
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
async turnOn() {
|
|
385
|
+
const body = this.buildBypassV2Payload('setSwitch', {
|
|
386
|
+
id: 0,
|
|
387
|
+
enabled: true
|
|
388
|
+
});
|
|
389
|
+
const [response] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
390
|
+
if (this.handleBypassV2Response(response, 'turn on')) {
|
|
391
|
+
this.deviceStatus = 'on';
|
|
392
|
+
this.connectionStatus = 'online';
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
async turnOff() {
|
|
398
|
+
const body = this.buildBypassV2Payload('setSwitch', {
|
|
399
|
+
id: 0,
|
|
400
|
+
enabled: false
|
|
401
|
+
});
|
|
402
|
+
const [response] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
403
|
+
if (this.handleBypassV2Response(response, 'turn off')) {
|
|
404
|
+
this.deviceStatus = 'off';
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
async setBrightness(brightness) {
|
|
410
|
+
const brightnessUpdate = vesyncBulb_1.VeSyncBulb.clamp(Math.round(brightness), 0, 100);
|
|
411
|
+
const body = this.buildBypassV2Payload('setLightStatus', {
|
|
412
|
+
action: 'on',
|
|
413
|
+
speed: 0,
|
|
414
|
+
brightness: brightnessUpdate,
|
|
415
|
+
red: 0,
|
|
416
|
+
green: 0,
|
|
417
|
+
blue: 0,
|
|
418
|
+
colorMode: 'white'
|
|
419
|
+
});
|
|
420
|
+
const [response] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
421
|
+
if (this.handleBypassV2Response(response, 'set brightness')) {
|
|
422
|
+
this.brightness = brightnessUpdate;
|
|
423
|
+
this.deviceStatus = 'on';
|
|
424
|
+
this.colorMode = 'white';
|
|
425
|
+
this.connectionStatus = 'online';
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
172
428
|
return false;
|
|
173
429
|
}
|
|
174
430
|
async setRgb(red, green, blue) {
|
|
@@ -176,47 +432,40 @@ class VeSyncBulbESL100MC extends vesyncBulb_1.VeSyncBulb {
|
|
|
176
432
|
logger_1.logger.error(`[${this.deviceName}] Color control not supported`);
|
|
177
433
|
return false;
|
|
178
434
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
435
|
+
const body = this.buildBypassV2Payload('setLightStatus', {
|
|
436
|
+
action: 'on',
|
|
437
|
+
speed: 0,
|
|
438
|
+
brightness: 100,
|
|
439
|
+
red: vesyncBulb_1.VeSyncBulb.clamp(Math.round(red), 0, 255),
|
|
440
|
+
green: vesyncBulb_1.VeSyncBulb.clamp(Math.round(green), 0, 255),
|
|
441
|
+
blue: vesyncBulb_1.VeSyncBulb.clamp(Math.round(blue), 0, 255),
|
|
442
|
+
colorMode: 'color'
|
|
443
|
+
});
|
|
444
|
+
const [response] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
445
|
+
if (this.handleBypassV2Response(response, 'set RGB color')) {
|
|
446
|
+
this.rgbValues = {
|
|
447
|
+
red: vesyncBulb_1.VeSyncBulb.clamp(Math.round(red), 0, 255),
|
|
448
|
+
green: vesyncBulb_1.VeSyncBulb.clamp(Math.round(green), 0, 255),
|
|
449
|
+
blue: vesyncBulb_1.VeSyncBulb.clamp(Math.round(blue), 0, 255)
|
|
450
|
+
};
|
|
451
|
+
this.brightness = 100;
|
|
452
|
+
this.deviceStatus = 'on';
|
|
453
|
+
this.colorMode = 'color';
|
|
454
|
+
this.connectionStatus = 'online';
|
|
196
455
|
return true;
|
|
197
456
|
}
|
|
198
|
-
logger_1.logger.error(`[${this.deviceName}] Failed to set RGB color: ${JSON.stringify(response)}`);
|
|
199
457
|
return false;
|
|
200
458
|
}
|
|
201
459
|
async enableWhiteMode() {
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
cid: this.cid,
|
|
206
|
-
configModule: this.configModule,
|
|
207
|
-
jsonCmd: {
|
|
208
|
-
light: {
|
|
209
|
-
mode: 'white'
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
const [response] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
|
|
214
|
-
if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
|
|
460
|
+
const targetBrightness = this.brightness > 0 ? this.brightness : 100;
|
|
461
|
+
const success = await this.setBrightness(targetBrightness);
|
|
462
|
+
if (success) {
|
|
215
463
|
this.colorMode = 'white';
|
|
216
|
-
logger_1.logger.info(`[${this.deviceName}] Successfully enabled white mode`);
|
|
217
|
-
return true;
|
|
218
464
|
}
|
|
219
|
-
|
|
465
|
+
return success;
|
|
466
|
+
}
|
|
467
|
+
async setColorTemp(colorTemp) {
|
|
468
|
+
logger_1.logger.error(`[${this.deviceName}] Color temperature control not supported`);
|
|
220
469
|
return false;
|
|
221
470
|
}
|
|
222
471
|
}
|
package/package.json
CHANGED
package/src/lib/vesyncBulb.ts
CHANGED
|
@@ -7,6 +7,12 @@ import { VeSync } from './vesync';
|
|
|
7
7
|
import { Helpers } from './helpers';
|
|
8
8
|
import { logger } from './logger';
|
|
9
9
|
|
|
10
|
+
interface RgbValues {
|
|
11
|
+
red: number;
|
|
12
|
+
green: number;
|
|
13
|
+
blue: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
interface BulbConfig {
|
|
11
17
|
[key: string]: {
|
|
12
18
|
module: string;
|
|
@@ -50,7 +56,7 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
50
56
|
protected colorSaturation: number;
|
|
51
57
|
protected colorMode: string;
|
|
52
58
|
protected features: string[];
|
|
53
|
-
protected rgbValues:
|
|
59
|
+
protected rgbValues: RgbValues;
|
|
54
60
|
|
|
55
61
|
constructor(details: Record<string, any>, manager: VeSync) {
|
|
56
62
|
super(details, manager);
|
|
@@ -68,6 +74,18 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
68
74
|
};
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
hasFeature(feature: string): boolean {
|
|
78
|
+
return this.features.includes(feature);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getColorModel(): string {
|
|
82
|
+
return bulbConfig[this.deviceType]?.colorModel ?? 'none';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getBrightness(): number {
|
|
86
|
+
return this.brightness;
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
/**
|
|
72
90
|
* Get bulb details
|
|
73
91
|
*/
|
|
@@ -101,29 +119,7 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
101
119
|
Helpers.reqHeaders(this.manager)
|
|
102
120
|
);
|
|
103
121
|
|
|
104
|
-
|
|
105
|
-
if (success && response?.result) {
|
|
106
|
-
this.deviceStatus = response.result.enabled ? 'on' : 'off';
|
|
107
|
-
this.brightness = response.result.brightness || this.brightness;
|
|
108
|
-
if (this.features.includes('color_temp')) {
|
|
109
|
-
this.colorTemp = response.result.colorTemp || this.colorTemp;
|
|
110
|
-
}
|
|
111
|
-
if (this.features.includes('rgb_shift')) {
|
|
112
|
-
if (bulbConfig[this.deviceType].colorModel === 'rgb') {
|
|
113
|
-
this.rgbValues = {
|
|
114
|
-
red: response.result.red || this.rgbValues.red,
|
|
115
|
-
green: response.result.green || this.rgbValues.green,
|
|
116
|
-
blue: response.result.blue || this.rgbValues.blue
|
|
117
|
-
};
|
|
118
|
-
} else {
|
|
119
|
-
this.colorHue = response.result.hue || this.colorHue;
|
|
120
|
-
this.colorSaturation = response.result.saturation || this.colorSaturation;
|
|
121
|
-
this.colorValue = response.result.value || this.colorValue;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
logger.debug(`[${this.deviceName}] Successfully retrieved bulb details`);
|
|
125
|
-
}
|
|
126
|
-
return success;
|
|
122
|
+
return this.processStandardBypassDetails(response, statusCode);
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
/**
|
|
@@ -239,7 +235,8 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
239
235
|
return false;
|
|
240
236
|
}
|
|
241
237
|
|
|
242
|
-
|
|
238
|
+
const brightnessLevel = VeSyncBulb.clamp(Math.round(brightness), 0, 100);
|
|
239
|
+
logger.debug(`[${this.deviceName}] Setting brightness to ${brightnessLevel}`);
|
|
243
240
|
|
|
244
241
|
const isV2Device = this.deviceType === 'XYD0001';
|
|
245
242
|
const endpoint = isV2Device ? '/cloud/v2/deviceManaged/bypassV2' : '/cloud/v1/deviceManaged/bypass';
|
|
@@ -249,7 +246,7 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
249
246
|
configModule: this.configModule,
|
|
250
247
|
payload: {
|
|
251
248
|
data: {
|
|
252
|
-
brightness:
|
|
249
|
+
brightness: brightnessLevel,
|
|
253
250
|
colorMode: '',
|
|
254
251
|
colorTemp: '',
|
|
255
252
|
force: 0,
|
|
@@ -266,7 +263,7 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
266
263
|
configModule: this.configModule,
|
|
267
264
|
jsonCmd: {
|
|
268
265
|
light: {
|
|
269
|
-
brightness:
|
|
266
|
+
brightness: brightnessLevel
|
|
270
267
|
}
|
|
271
268
|
}
|
|
272
269
|
};
|
|
@@ -279,8 +276,8 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
279
276
|
);
|
|
280
277
|
|
|
281
278
|
if (response?.code === 0) {
|
|
282
|
-
this.brightness =
|
|
283
|
-
logger.info(`[${this.deviceName}] Successfully set brightness to ${
|
|
279
|
+
this.brightness = brightnessLevel;
|
|
280
|
+
logger.info(`[${this.deviceName}] Successfully set brightness to ${brightnessLevel}`);
|
|
284
281
|
return true;
|
|
285
282
|
}
|
|
286
283
|
logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
|
|
@@ -290,10 +287,6 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
290
287
|
/**
|
|
291
288
|
* Get bulb brightness
|
|
292
289
|
*/
|
|
293
|
-
getBrightness(): number {
|
|
294
|
-
return this.brightness;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
290
|
/**
|
|
298
291
|
* Get color temperature in Kelvin
|
|
299
292
|
*/
|
|
@@ -354,8 +347,141 @@ export abstract class VeSyncBulb extends VeSyncBaseDevice {
|
|
|
354
347
|
return this.rgbValues;
|
|
355
348
|
}
|
|
356
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Set color temperature wrapper to maintain compatibility with consumers
|
|
352
|
+
*/
|
|
353
|
+
async setColorTemperature(colorTemp: number): Promise<boolean> {
|
|
354
|
+
return this.setColorTemp(colorTemp);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Set color via hue/saturation/value when supported
|
|
359
|
+
*/
|
|
360
|
+
async setColor(hue: number, saturation: number, value: number = 100): Promise<boolean> {
|
|
361
|
+
if (!this.features.includes('rgb_shift')) {
|
|
362
|
+
logger.error(`[${this.deviceName}] Color control not supported`);
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const colorModel = bulbConfig[this.deviceType]?.colorModel;
|
|
367
|
+
if (colorModel === 'rgb' && typeof (this as any).setRgb === 'function') {
|
|
368
|
+
const rgb = VeSyncBulb.hsvToRgb(hue, saturation, value);
|
|
369
|
+
return (this as any).setRgb(rgb.red, rgb.green, rgb.blue);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (typeof (this as any).setHsv === 'function') {
|
|
373
|
+
return (this as any).setHsv(hue, saturation, value);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
logger.error(`[${this.deviceName}] No compatible color handler found`);
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Process standard bypass (v1) detail responses shared by multiple bulbs
|
|
382
|
+
*/
|
|
383
|
+
protected processStandardBypassDetails(response: any, statusCode: number): boolean {
|
|
384
|
+
const success = this.checkResponse([response, statusCode], 'getDetails');
|
|
385
|
+
if (success && response?.result) {
|
|
386
|
+
const innerResult = response.result?.result || response.result;
|
|
387
|
+
if (innerResult) {
|
|
388
|
+
if (typeof innerResult.enabled === 'boolean') {
|
|
389
|
+
this.deviceStatus = innerResult.enabled ? 'on' : 'off';
|
|
390
|
+
} else if (innerResult.action) {
|
|
391
|
+
this.deviceStatus = innerResult.action === 'on' ? 'on' : 'off';
|
|
392
|
+
}
|
|
393
|
+
if (innerResult.connectionStatus) {
|
|
394
|
+
this.connectionStatus = innerResult.connectionStatus;
|
|
395
|
+
}
|
|
396
|
+
if (this.features.includes('dimmable') && innerResult.brightness !== undefined) {
|
|
397
|
+
this.brightness = Number(innerResult.brightness);
|
|
398
|
+
}
|
|
399
|
+
if (this.features.includes('color_temp')) {
|
|
400
|
+
if (innerResult.colorTemp !== undefined) {
|
|
401
|
+
this.colorTemp = Number(innerResult.colorTemp);
|
|
402
|
+
} else if (innerResult.colorTempe !== undefined) {
|
|
403
|
+
this.colorTemp = Number(innerResult.colorTempe);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (this.features.includes('rgb_shift')) {
|
|
407
|
+
if (bulbConfig[this.deviceType]?.colorModel === 'rgb') {
|
|
408
|
+
this.rgbValues = {
|
|
409
|
+
red: Number(innerResult.red ?? this.rgbValues.red),
|
|
410
|
+
green: Number(innerResult.green ?? this.rgbValues.green),
|
|
411
|
+
blue: Number(innerResult.blue ?? this.rgbValues.blue)
|
|
412
|
+
};
|
|
413
|
+
} else {
|
|
414
|
+
this.colorHue = Number(innerResult.hue ?? this.colorHue);
|
|
415
|
+
this.colorSaturation = Number(innerResult.saturation ?? this.colorSaturation);
|
|
416
|
+
this.colorValue = Number(innerResult.value ?? this.colorValue);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
logger.debug(`[${this.deviceName}] Successfully retrieved bulb details`);
|
|
421
|
+
}
|
|
422
|
+
return success;
|
|
423
|
+
}
|
|
424
|
+
|
|
357
425
|
/**
|
|
358
426
|
* Set color temperature - Abstract method to be implemented by subclasses
|
|
359
427
|
*/
|
|
360
428
|
abstract setColorTemp(colorTemp: number): Promise<boolean>;
|
|
361
|
-
|
|
429
|
+
|
|
430
|
+
protected static clamp(value: number, min: number, max: number): number {
|
|
431
|
+
return Math.min(Math.max(value, min), max);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
protected static hsvToRgb(h: number, s: number, v: number): RgbValues {
|
|
435
|
+
const saturation = VeSyncBulb.clamp(s, 0, 100) / 100;
|
|
436
|
+
const value = VeSyncBulb.clamp(v, 0, 100) / 100;
|
|
437
|
+
const hue = ((h % 360) + 360) % 360 / 60;
|
|
438
|
+
const i = Math.floor(hue);
|
|
439
|
+
const f = hue - i;
|
|
440
|
+
const p = value * (1 - saturation);
|
|
441
|
+
const q = value * (1 - saturation * f);
|
|
442
|
+
const t = value * (1 - saturation * (1 - f));
|
|
443
|
+
|
|
444
|
+
let r = 0;
|
|
445
|
+
let g = 0;
|
|
446
|
+
let b = 0;
|
|
447
|
+
|
|
448
|
+
switch (i) {
|
|
449
|
+
case 0:
|
|
450
|
+
r = value;
|
|
451
|
+
g = t;
|
|
452
|
+
b = p;
|
|
453
|
+
break;
|
|
454
|
+
case 1:
|
|
455
|
+
r = q;
|
|
456
|
+
g = value;
|
|
457
|
+
b = p;
|
|
458
|
+
break;
|
|
459
|
+
case 2:
|
|
460
|
+
r = p;
|
|
461
|
+
g = value;
|
|
462
|
+
b = t;
|
|
463
|
+
break;
|
|
464
|
+
case 3:
|
|
465
|
+
r = p;
|
|
466
|
+
g = q;
|
|
467
|
+
b = value;
|
|
468
|
+
break;
|
|
469
|
+
case 4:
|
|
470
|
+
r = t;
|
|
471
|
+
g = p;
|
|
472
|
+
b = value;
|
|
473
|
+
break;
|
|
474
|
+
default:
|
|
475
|
+
r = value;
|
|
476
|
+
g = p;
|
|
477
|
+
b = q;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
red: Math.round(r * 255),
|
|
483
|
+
green: Math.round(g * 255),
|
|
484
|
+
blue: Math.round(b * 255)
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
}
|