tsvesync 1.3.9 → 1.3.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.
@@ -2,7 +2,7 @@
2
2
  * VeSync Bulb Implementations
3
3
  */
4
4
 
5
- import { VeSyncBulb, bulbConfig } from './vesyncBulb';
5
+ import { VeSyncBulb } from './vesyncBulb';
6
6
  import { VeSync } from './vesync';
7
7
  import { Helpers } from './helpers';
8
8
  import { logger } from './logger';
@@ -15,6 +15,122 @@ export class VeSyncBulbESL100 extends VeSyncBulb {
15
15
  super(details, manager);
16
16
  }
17
17
 
18
+ async getDetails(): Promise<boolean> {
19
+ logger.debug(`[${this.deviceName}] Fetching ESL100 details`);
20
+ const body = {
21
+ ...Helpers.reqBody(this.manager, 'devicedetail'),
22
+ uuid: this.uuid
23
+ };
24
+
25
+ const [response, statusCode] = await this.callApi(
26
+ '/SmartBulb/v1/device/devicedetail',
27
+ 'post',
28
+ body,
29
+ Helpers.reqHeaders(this.manager)
30
+ );
31
+
32
+ const success = this.checkResponse([response, statusCode], 'getDetails');
33
+ if (success && response?.result) {
34
+ const result = response.result;
35
+ if (result.deviceStatus) {
36
+ this.deviceStatus = result.deviceStatus;
37
+ }
38
+ if (result.connectionStatus) {
39
+ this.connectionStatus = result.connectionStatus;
40
+ }
41
+ if (result.brightNess !== undefined) {
42
+ this.brightness = Number(result.brightNess);
43
+ } else if (result.brightness !== undefined) {
44
+ this.brightness = Number(result.brightness);
45
+ }
46
+ logger.debug(`[${this.deviceName}] Updated details: status=${this.deviceStatus}, brightness=${this.brightness}`);
47
+ }
48
+
49
+ return success;
50
+ }
51
+
52
+ async turnOn(): Promise<boolean> {
53
+ const body = {
54
+ ...Helpers.reqBody(this.manager, 'devicestatus'),
55
+ uuid: this.uuid,
56
+ status: 'on'
57
+ };
58
+
59
+ const [response] = await this.callApi(
60
+ '/SmartBulb/v1/device/devicestatus',
61
+ 'put',
62
+ body,
63
+ Helpers.reqHeaders(this.manager)
64
+ );
65
+
66
+ if (response?.code === 0) {
67
+ this.deviceStatus = 'on';
68
+ this.connectionStatus = 'online';
69
+ logger.info(`[${this.deviceName}] Turned on successfully`);
70
+ return true;
71
+ }
72
+
73
+ logger.error(`[${this.deviceName}] Failed to turn on: ${JSON.stringify(response)}`);
74
+ return false;
75
+ }
76
+
77
+ async turnOff(): Promise<boolean> {
78
+ const body = {
79
+ ...Helpers.reqBody(this.manager, 'devicestatus'),
80
+ uuid: this.uuid,
81
+ status: 'off'
82
+ };
83
+
84
+ const [response] = await this.callApi(
85
+ '/SmartBulb/v1/device/devicestatus',
86
+ 'put',
87
+ body,
88
+ Helpers.reqHeaders(this.manager)
89
+ );
90
+
91
+ if (response?.code === 0) {
92
+ this.deviceStatus = 'off';
93
+ logger.info(`[${this.deviceName}] Turned off successfully`);
94
+ return true;
95
+ }
96
+
97
+ logger.error(`[${this.deviceName}] Failed to turn off: ${JSON.stringify(response)}`);
98
+ return false;
99
+ }
100
+
101
+ async setBrightness(brightness: number): Promise<boolean> {
102
+ if (!this.features.includes('dimmable')) {
103
+ logger.error(`[${this.deviceName}] Dimming not supported`);
104
+ return false;
105
+ }
106
+
107
+ const brightnessUpdate = VeSyncBulb.clamp(Math.round(brightness), 0, 100);
108
+ const body = {
109
+ ...Helpers.reqBody(this.manager, 'devicestatus'),
110
+ uuid: this.uuid,
111
+ status: 'on',
112
+ brightNess: String(brightnessUpdate)
113
+ };
114
+
115
+ const [response] = await this.callApi(
116
+ '/SmartBulb/v1/device/updateBrightness',
117
+ 'put',
118
+ body,
119
+ Helpers.reqHeaders(this.manager)
120
+ );
121
+
122
+ if (response?.code === 0) {
123
+ this.brightness = brightnessUpdate;
124
+ this.deviceStatus = 'on';
125
+ this.connectionStatus = 'online';
126
+ logger.info(`[${this.deviceName}] Brightness set to ${brightnessUpdate}`);
127
+ return true;
128
+ }
129
+
130
+ logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
131
+ return false;
132
+ }
133
+
18
134
  async setColorTemp(colorTemp: number): Promise<boolean> {
19
135
  logger.error(`[${this.deviceName}] Color temperature control not supported`);
20
136
  return false;
@@ -29,36 +145,153 @@ export class VeSyncBulbESL100CW extends VeSyncBulb {
29
145
  super(details, manager);
30
146
  }
31
147
 
32
- async setColorTemp(colorTemp: number): Promise<boolean> {
33
- if (!this.features.includes('color_temp')) {
34
- logger.error(`[${this.deviceName}] Color temperature control not supported`);
35
- return false;
36
- }
148
+ private buildBypassBody(lightPayload: Record<string, unknown>): Record<string, unknown> {
149
+ return {
150
+ ...Helpers.reqBody(this.manager, 'bypass'),
151
+ cid: this.cid,
152
+ configModule: this.configModule,
153
+ jsonCmd: {
154
+ light: lightPayload
155
+ }
156
+ };
157
+ }
37
158
 
38
- logger.debug(`[${this.deviceName}] Setting color temperature to ${colorTemp}`);
159
+ async getDetails(): Promise<boolean> {
160
+ logger.debug(`[${this.deviceName}] Fetching ESL100CW status`);
39
161
  const body = {
40
162
  ...Helpers.reqBody(this.manager, 'bypass'),
41
163
  cid: this.cid,
42
164
  configModule: this.configModule,
43
165
  jsonCmd: {
44
- light: {
45
- colorTempe: colorTemp
166
+ getLightStatus: 'get'
167
+ }
168
+ };
169
+
170
+ const [response, statusCode] = await this.callApi(
171
+ '/cloud/v1/deviceManaged/bypass',
172
+ 'post',
173
+ body,
174
+ Helpers.reqHeaders(this.manager)
175
+ );
176
+
177
+ const success = this.checkResponse([response, statusCode], 'getDetails');
178
+ if (success) {
179
+ const inner = response?.result?.result || response?.result;
180
+ const light = inner?.light;
181
+ if (light) {
182
+ this.deviceStatus = light.action === 'on' ? 'on' : 'off';
183
+ if (light.brightness !== undefined) {
184
+ this.brightness = Number(light.brightness);
185
+ }
186
+ if (light.colorTempe !== undefined) {
187
+ this.colorTemp = Number(light.colorTempe);
46
188
  }
47
189
  }
190
+ }
191
+
192
+ return success;
193
+ }
194
+
195
+ async turnOn(): Promise<boolean> {
196
+ const [response] = await this.callApi(
197
+ '/cloud/v1/deviceManaged/bypass',
198
+ 'post',
199
+ this.buildBypassBody({ action: 'on' }),
200
+ Helpers.reqHeaders(this.manager)
201
+ );
202
+
203
+ if (response?.code === 0) {
204
+ this.deviceStatus = 'on';
205
+ this.connectionStatus = 'online';
206
+ logger.info(`[${this.deviceName}] Turned on successfully`);
207
+ return true;
208
+ }
209
+
210
+ logger.error(`[${this.deviceName}] Failed to turn on: ${JSON.stringify(response)}`);
211
+ return false;
212
+ }
213
+
214
+ async turnOff(): Promise<boolean> {
215
+ const [response] = await this.callApi(
216
+ '/cloud/v1/deviceManaged/bypass',
217
+ 'post',
218
+ this.buildBypassBody({ action: 'off' }),
219
+ Helpers.reqHeaders(this.manager)
220
+ );
221
+
222
+ if (response?.code === 0) {
223
+ this.deviceStatus = 'off';
224
+ logger.info(`[${this.deviceName}] Turned off successfully`);
225
+ return true;
226
+ }
227
+
228
+ logger.error(`[${this.deviceName}] Failed to turn off: ${JSON.stringify(response)}`);
229
+ return false;
230
+ }
231
+
232
+ async setBrightness(brightness: number): Promise<boolean> {
233
+ if (!this.features.includes('dimmable')) {
234
+ logger.error(`[${this.deviceName}] Dimming not supported`);
235
+ return false;
236
+ }
237
+
238
+ const brightnessUpdate = VeSyncBulb.clamp(Math.round(brightness), 0, 100);
239
+ const light: Record<string, unknown> = {
240
+ action: 'on',
241
+ brightness: brightnessUpdate
48
242
  };
49
243
 
244
+ if (this.features.includes('color_temp')) {
245
+ light.colorTempe = this.colorTemp;
246
+ }
247
+
50
248
  const [response] = await this.callApi(
51
249
  '/cloud/v1/deviceManaged/bypass',
52
250
  'post',
53
- body,
251
+ this.buildBypassBody(light),
54
252
  Helpers.reqHeaders(this.manager)
55
253
  );
56
254
 
57
255
  if (response?.code === 0) {
58
- this.colorTemp = colorTemp;
59
- logger.info(`[${this.deviceName}] Successfully set color temperature to ${colorTemp}`);
256
+ this.brightness = brightnessUpdate;
257
+ this.deviceStatus = 'on';
258
+ this.connectionStatus = 'online';
259
+ logger.info(`[${this.deviceName}] Brightness set to ${brightnessUpdate}`);
60
260
  return true;
61
261
  }
262
+
263
+ logger.error(`[${this.deviceName}] Failed to set brightness: ${JSON.stringify(response)}`);
264
+ return false;
265
+ }
266
+
267
+ async setColorTemp(colorTemp: number): Promise<boolean> {
268
+ if (!this.features.includes('color_temp')) {
269
+ logger.error(`[${this.deviceName}] Color temperature control not supported`);
270
+ return false;
271
+ }
272
+
273
+ const colorTempUpdate = VeSyncBulb.clamp(Math.round(colorTemp), 0, 100);
274
+ const light = {
275
+ action: 'on',
276
+ brightness: this.brightness,
277
+ colorTempe: colorTempUpdate
278
+ };
279
+
280
+ const [response] = await this.callApi(
281
+ '/cloud/v1/deviceManaged/bypass',
282
+ 'post',
283
+ this.buildBypassBody(light),
284
+ Helpers.reqHeaders(this.manager)
285
+ );
286
+
287
+ if (response?.code === 0) {
288
+ this.colorTemp = colorTempUpdate;
289
+ this.deviceStatus = 'on';
290
+ this.connectionStatus = 'online';
291
+ logger.info(`[${this.deviceName}] Color temperature set to ${colorTempUpdate}`);
292
+ return true;
293
+ }
294
+
62
295
  logger.error(`[${this.deviceName}] Failed to set color temperature: ${JSON.stringify(response)}`);
63
296
  return false;
64
297
  }
@@ -204,73 +437,192 @@ export class VeSyncBulbESL100MC extends VeSyncBulb {
204
437
  super(details, manager);
205
438
  }
206
439
 
207
- async setColorTemp(colorTemp: number): Promise<boolean> {
208
- logger.error(`[${this.deviceName}] Color temperature control not supported`);
209
- return false;
440
+ private buildBypassV2Payload(method: string, data: Record<string, unknown>): Record<string, unknown> {
441
+ return {
442
+ ...Helpers.reqBody(this.manager, 'bypassV2'),
443
+ cid: this.cid,
444
+ configModule: this.configModule,
445
+ payload: {
446
+ method,
447
+ source: 'APP',
448
+ data
449
+ }
450
+ };
210
451
  }
211
452
 
212
- async setRgb(red: number, green: number, blue: number): Promise<boolean> {
213
- if (!this.features.includes('rgb_shift')) {
214
- logger.error(`[${this.deviceName}] Color control not supported`);
453
+ private handleBypassV2Response(response: any, context: string): boolean {
454
+ if (!response) {
455
+ logger.error(`[${this.deviceName}] ${context} returned empty response`);
215
456
  return false;
216
457
  }
217
458
 
218
- logger.debug(`[${this.deviceName}] Setting RGB color to R:${red} G:${green} B:${blue}`);
219
- const body = {
220
- ...Helpers.reqBody(this.manager, 'bypass'),
221
- cid: this.cid,
222
- configModule: this.configModule,
223
- jsonCmd: {
224
- light: {
225
- red: Math.round(red),
226
- green: Math.round(green),
227
- blue: Math.round(blue)
228
- }
459
+ if (response.code !== 0) {
460
+ logger.error(`[${this.deviceName}] ${context} failed: ${JSON.stringify(response)}`);
461
+ return false;
462
+ }
463
+
464
+ const inner = response.result;
465
+ if (inner?.code !== undefined && inner.code !== 0) {
466
+ logger.error(`[${this.deviceName}] ${context} inner error: ${JSON.stringify(response)}`);
467
+ return false;
468
+ }
469
+
470
+ return true;
471
+ }
472
+
473
+ async getDetails(): Promise<boolean> {
474
+ logger.debug(`[${this.deviceName}] Fetching ESL100MC status`);
475
+ const body = this.buildBypassV2Payload('getLightStatus', {});
476
+ const [response, statusCode] = await this.callApi(
477
+ '/cloud/v2/deviceManaged/bypassV2',
478
+ 'post',
479
+ body,
480
+ Helpers.reqHeaders(this.manager)
481
+ );
482
+
483
+ const success = this.checkResponse([response, statusCode], 'getDetails');
484
+ if (!success) {
485
+ return false;
486
+ }
487
+
488
+ const inner = response?.result?.result || response?.result;
489
+ if (inner) {
490
+ if (inner.action) {
491
+ this.deviceStatus = inner.action === 'on' ? 'on' : 'off';
229
492
  }
230
- };
493
+ if (inner.brightness !== undefined) {
494
+ this.brightness = Number(inner.brightness);
495
+ }
496
+ this.rgbValues = {
497
+ red: Number(inner.red ?? this.rgbValues.red),
498
+ green: Number(inner.green ?? this.rgbValues.green),
499
+ blue: Number(inner.blue ?? this.rgbValues.blue)
500
+ };
501
+ this.colorMode = inner.colorMode || this.colorMode;
502
+ }
503
+
504
+ return true;
505
+ }
506
+
507
+ async turnOn(): Promise<boolean> {
508
+ const body = this.buildBypassV2Payload('setSwitch', {
509
+ id: 0,
510
+ enabled: true
511
+ });
231
512
 
232
513
  const [response] = await this.callApi(
233
- '/cloud/v1/deviceManaged/bypass',
514
+ '/cloud/v2/deviceManaged/bypassV2',
234
515
  'post',
235
516
  body,
236
517
  Helpers.reqHeaders(this.manager)
237
518
  );
238
519
 
239
- if (response?.code === 0) {
240
- this.rgbValues = { red, green, blue };
241
- logger.info(`[${this.deviceName}] Successfully set RGB color`);
520
+ if (this.handleBypassV2Response(response, 'turn on')) {
521
+ this.deviceStatus = 'on';
522
+ this.connectionStatus = 'online';
242
523
  return true;
243
524
  }
244
- logger.error(`[${this.deviceName}] Failed to set RGB color: ${JSON.stringify(response)}`);
245
525
  return false;
246
526
  }
247
527
 
248
- async enableWhiteMode(): Promise<boolean> {
249
- logger.debug(`[${this.deviceName}] Enabling white mode`);
250
- const body = {
251
- ...Helpers.reqBody(this.manager, 'bypass'),
252
- cid: this.cid,
253
- configModule: this.configModule,
254
- jsonCmd: {
255
- light: {
256
- mode: 'white'
257
- }
258
- }
259
- };
528
+ async turnOff(): Promise<boolean> {
529
+ const body = this.buildBypassV2Payload('setSwitch', {
530
+ id: 0,
531
+ enabled: false
532
+ });
260
533
 
261
534
  const [response] = await this.callApi(
262
- '/cloud/v1/deviceManaged/bypass',
535
+ '/cloud/v2/deviceManaged/bypassV2',
263
536
  'post',
264
537
  body,
265
538
  Helpers.reqHeaders(this.manager)
266
539
  );
267
540
 
268
- if (response?.code === 0) {
541
+ if (this.handleBypassV2Response(response, 'turn off')) {
542
+ this.deviceStatus = 'off';
543
+ return true;
544
+ }
545
+ return false;
546
+ }
547
+
548
+ async setBrightness(brightness: number): Promise<boolean> {
549
+ const brightnessUpdate = VeSyncBulb.clamp(Math.round(brightness), 0, 100);
550
+ const body = this.buildBypassV2Payload('setLightStatus', {
551
+ action: 'on',
552
+ speed: 0,
553
+ brightness: brightnessUpdate,
554
+ red: 0,
555
+ green: 0,
556
+ blue: 0,
557
+ colorMode: 'white'
558
+ });
559
+
560
+ const [response] = await this.callApi(
561
+ '/cloud/v2/deviceManaged/bypassV2',
562
+ 'post',
563
+ body,
564
+ Helpers.reqHeaders(this.manager)
565
+ );
566
+
567
+ if (this.handleBypassV2Response(response, 'set brightness')) {
568
+ this.brightness = brightnessUpdate;
569
+ this.deviceStatus = 'on';
269
570
  this.colorMode = 'white';
270
- logger.info(`[${this.deviceName}] Successfully enabled white mode`);
571
+ this.connectionStatus = 'online';
271
572
  return true;
272
573
  }
273
- logger.error(`[${this.deviceName}] Failed to enable white mode: ${JSON.stringify(response)}`);
574
+ return false;
575
+ }
576
+
577
+ async setRgb(red: number, green: number, blue: number): Promise<boolean> {
578
+ if (!this.features.includes('rgb_shift')) {
579
+ logger.error(`[${this.deviceName}] Color control not supported`);
580
+ return false;
581
+ }
582
+
583
+ const body = this.buildBypassV2Payload('setLightStatus', {
584
+ action: 'on',
585
+ speed: 0,
586
+ brightness: 100,
587
+ red: VeSyncBulb.clamp(Math.round(red), 0, 255),
588
+ green: VeSyncBulb.clamp(Math.round(green), 0, 255),
589
+ blue: VeSyncBulb.clamp(Math.round(blue), 0, 255),
590
+ colorMode: 'color'
591
+ });
592
+
593
+ const [response] = await this.callApi(
594
+ '/cloud/v2/deviceManaged/bypassV2',
595
+ 'post',
596
+ body,
597
+ Helpers.reqHeaders(this.manager)
598
+ );
599
+
600
+ if (this.handleBypassV2Response(response, 'set RGB color')) {
601
+ this.rgbValues = {
602
+ red: VeSyncBulb.clamp(Math.round(red), 0, 255),
603
+ green: VeSyncBulb.clamp(Math.round(green), 0, 255),
604
+ blue: VeSyncBulb.clamp(Math.round(blue), 0, 255)
605
+ };
606
+ this.brightness = 100;
607
+ this.deviceStatus = 'on';
608
+ this.colorMode = 'color';
609
+ this.connectionStatus = 'online';
610
+ return true;
611
+ }
612
+ return false;
613
+ }
614
+
615
+ async enableWhiteMode(): Promise<boolean> {
616
+ const targetBrightness = this.brightness > 0 ? this.brightness : 100;
617
+ const success = await this.setBrightness(targetBrightness);
618
+ if (success) {
619
+ this.colorMode = 'white';
620
+ }
621
+ return success;
622
+ }
623
+
624
+ async setColorTemp(colorTemp: number): Promise<boolean> {
625
+ logger.error(`[${this.deviceName}] Color temperature control not supported`);
274
626
  return false;
275
627
  }
276
628
  }
@@ -281,4 +633,4 @@ export const bulbModules: Record<string, new (details: Record<string, any>, mana
281
633
  'ESL100CW': VeSyncBulbESL100CW,
282
634
  'XYD0001': VeSyncBulbXYD0001,
283
635
  'ESL100MC': VeSyncBulbESL100MC
284
- };
636
+ };