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.
@@ -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
- async setColorTemp(colorTemp) {
31
- if (!this.features.includes('color_temp')) {
32
- logger_1.logger.error(`[${this.deviceName}] Color temperature control not supported`);
33
- return false;
34
- }
35
- logger_1.logger.debug(`[${this.deviceName}] Setting color temperature to ${colorTemp}`);
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
- light: {
42
- colorTempe: colorTemp
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
- const [response] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
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.colorTemp = colorTemp;
49
- logger_1.logger.info(`[${this.deviceName}] Successfully set color temperature to ${colorTemp}`);
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
- async setColorTemp(colorTemp) {
171
- logger_1.logger.error(`[${this.deviceName}] Color temperature control not supported`);
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
- logger_1.logger.debug(`[${this.deviceName}] Setting RGB color to R:${red} G:${green} B:${blue}`);
180
- const body = {
181
- ...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
182
- cid: this.cid,
183
- configModule: this.configModule,
184
- jsonCmd: {
185
- light: {
186
- red: Math.round(red),
187
- green: Math.round(green),
188
- blue: Math.round(blue)
189
- }
190
- }
191
- };
192
- const [response] = await this.callApi('/cloud/v1/deviceManaged/bypass', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
193
- if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
194
- this.rgbValues = { red, green, blue };
195
- logger_1.logger.info(`[${this.deviceName}] Successfully set RGB color`);
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
- logger_1.logger.debug(`[${this.deviceName}] Enabling white mode`);
203
- const body = {
204
- ...helpers_1.Helpers.reqBody(this.manager, 'bypass'),
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
- logger_1.logger.error(`[${this.deviceName}] Failed to enable white mode: ${JSON.stringify(response)}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.3.9",
3
+ "version": "1.3.10",
4
4
  "description": "A TypeScript library for interacting with VeSync smart home devices",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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: { red: number, green: number, blue: number };
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
- const success = this.checkResponse([response, statusCode], 'getDetails');
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
- logger.debug(`[${this.deviceName}] Setting brightness to ${brightness}`);
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: 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: 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 = brightness;
283
- logger.info(`[${this.deviceName}] Successfully set brightness to ${brightness}`);
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
+ }