homebridge-tsvesync 1.3.3 → 1.3.4

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.
@@ -1,7 +1,7 @@
1
- import { CharacteristicValue, PlatformAccessory } from 'homebridge';
1
+ import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
2
2
  import { BaseAccessory } from './base.accessory';
3
3
  import { TSVESyncPlatform } from '../platform';
4
- import { DeviceCapabilities, VeSyncBulb } from '../types/device.types';
4
+ import { DeviceCapabilities, VeSyncBulb, VeSyncDimmerSwitch, VeSyncLightDevice } from '../types/device.types';
5
5
 
6
6
  // Constants for color temperature
7
7
  const MIN_COLOR_TEMP = 140; // 7143K (cool)
@@ -9,16 +9,23 @@ const MAX_COLOR_TEMP = 500; // 2000K (warm)
9
9
  const DEFAULT_COLOR_TEMP = MIN_COLOR_TEMP;
10
10
 
11
11
  export class LightAccessory extends BaseAccessory {
12
- protected readonly device: VeSyncBulb;
12
+ protected readonly device: VeSyncLightDevice;
13
13
  private readonly capabilities: DeviceCapabilities;
14
+ private readonly isDimmerDevice: boolean;
15
+ private indicatorService?: Service;
16
+ private indicatorColorState = { hue: 0, saturation: 0 };
17
+ private indicatorColorUpdateTimeout?: NodeJS.Timeout;
18
+ private lastDimmerRefresh = 0;
19
+ private static readonly DIMMER_REFRESH_DEBOUNCE_MS = 2000;
14
20
 
15
21
  constructor(
16
22
  platform: TSVESyncPlatform,
17
23
  accessory: PlatformAccessory,
18
- device: VeSyncBulb
24
+ device: VeSyncLightDevice
19
25
  ) {
20
26
  super(platform, accessory, device);
21
27
  this.device = device;
28
+ this.isDimmerDevice = this.detectDimmer(device);
22
29
  this.capabilities = this.getDeviceCapabilities();
23
30
  }
24
31
 
@@ -35,7 +42,7 @@ export class LightAccessory extends BaseAccessory {
35
42
  );
36
43
 
37
44
  // Set up optional characteristics based on device capabilities
38
- const capabilities = this.getDeviceCapabilities();
45
+ const capabilities = this.capabilities;
39
46
 
40
47
  if (capabilities.hasBrightness) {
41
48
  this.setupCharacteristic(
@@ -58,6 +65,10 @@ export class LightAccessory extends BaseAccessory {
58
65
  this.platform.Characteristic.Name,
59
66
  async () => this.device.deviceName
60
67
  );
68
+
69
+ if (this.isDimmerDevice) {
70
+ this.setupIndicatorService();
71
+ }
61
72
  }
62
73
 
63
74
  private setupColorTemperature(): void {
@@ -92,9 +103,7 @@ export class LightAccessory extends BaseAccessory {
92
103
  * Update device states based on the latest details
93
104
  */
94
105
  protected async updateDeviceSpecificStates(details: any): Promise<void> {
95
- const lightDetails = details as {
96
- deviceStatus: string;
97
- brightness?: number;
106
+ const lightDetails = details as VeSyncLightDevice & {
98
107
  colorTemp?: number;
99
108
  hue?: number;
100
109
  saturation?: number;
@@ -138,10 +147,41 @@ export class LightAccessory extends BaseAccessory {
138
147
  );
139
148
  }
140
149
  }
150
+
151
+ if (this.isDimmerDevice && this.indicatorService) {
152
+ const dimmer = lightDetails as VeSyncDimmerSwitch;
153
+ const isRgbOn = dimmer.rgbLightStatus === 'on';
154
+ this.updateIndicatorCharacteristic(
155
+ this.platform.Characteristic.On,
156
+ isRgbOn
157
+ );
158
+
159
+ const { hue, saturation } = this.rgbToHsv(
160
+ dimmer.rgbLightValue.red,
161
+ dimmer.rgbLightValue.green,
162
+ dimmer.rgbLightValue.blue
163
+ );
164
+ this.indicatorColorState = { hue, saturation };
165
+ this.updateIndicatorCharacteristic(this.platform.Characteristic.Hue, hue);
166
+ this.updateIndicatorCharacteristic(this.platform.Characteristic.Saturation, saturation);
167
+ }
141
168
  }
142
169
 
143
170
  protected getDeviceCapabilities(): DeviceCapabilities {
144
- // Determine capabilities based on model
171
+ if (this.isDimmerDevice) {
172
+ return {
173
+ hasBrightness: true,
174
+ hasColorTemp: false,
175
+ hasColor: false,
176
+ hasSpeed: false,
177
+ hasHumidity: false,
178
+ hasAirQuality: false,
179
+ hasWaterLevel: false,
180
+ hasChildLock: false,
181
+ hasSwingMode: false,
182
+ };
183
+ }
184
+
145
185
  const model = this.device.deviceType.toUpperCase();
146
186
  return {
147
187
  hasBrightness: true, // All VeSync bulbs support brightness
@@ -170,6 +210,10 @@ export class LightAccessory extends BaseAccessory {
170
210
  }
171
211
 
172
212
  await this.persistDeviceState('deviceStatus', isOn ? 'on' : 'off');
213
+
214
+ if (this.isDimmerDevice) {
215
+ await this.refreshDimmerDetails();
216
+ }
173
217
  } catch (error) {
174
218
  this.handleDeviceError('set on state', error);
175
219
  }
@@ -181,34 +225,67 @@ export class LightAccessory extends BaseAccessory {
181
225
 
182
226
  private async setBrightness(value: CharacteristicValue): Promise<void> {
183
227
  try {
184
- const success = await this.device.setBrightness(value as number);
185
-
228
+ const target = Math.round(Number(value));
229
+
230
+ if (this.isDimmerDevice && target <= 0) {
231
+ const success = await this.device.turnOff();
232
+ if (!success) {
233
+ throw new Error('Failed to turn off device when setting brightness to 0');
234
+ }
235
+ await this.persistDeviceState('deviceStatus', 'off');
236
+ await this.persistDeviceState('brightness', 0);
237
+ await this.refreshDimmerDetails();
238
+ return;
239
+ }
240
+
241
+ const brightness = Math.min(100, Math.max(this.isDimmerDevice ? 1 : 0, target));
242
+
243
+ const success = await this.device.setBrightness(brightness);
244
+
186
245
  if (!success) {
187
- throw new Error(`Failed to set brightness to ${value}`);
246
+ throw new Error(`Failed to set brightness to ${brightness}`);
247
+ }
248
+
249
+ await this.persistDeviceState('brightness', brightness);
250
+ if (this.isDimmerDevice && brightness > 0) {
251
+ await this.persistDeviceState('deviceStatus', 'on');
252
+ }
253
+
254
+ if (this.isDimmerDevice) {
255
+ await this.refreshDimmerDetails();
188
256
  }
189
-
190
- await this.persistDeviceState('brightness', value);
191
257
  } catch (error) {
192
258
  this.handleDeviceError('set brightness', error);
193
259
  }
194
260
  }
195
261
 
196
262
  private async getColorTemperature(): Promise<CharacteristicValue> {
197
- return this.device.colorTemp || DEFAULT_COLOR_TEMP;
263
+ if (this.isDimmerDevice) {
264
+ return DEFAULT_COLOR_TEMP;
265
+ }
266
+
267
+ const bulb = this.device as VeSyncBulb;
268
+ return bulb.colorTemp || DEFAULT_COLOR_TEMP;
198
269
  }
199
270
 
200
271
  private async setColorTemperature(value: CharacteristicValue): Promise<void> {
201
272
  try {
202
- if (!this.device.setColorTemperature) {
273
+ if (this.isDimmerDevice) {
203
274
  throw new Error('Device does not support color temperature');
204
275
  }
205
-
206
- const success = await this.device.setColorTemperature(value as number);
207
-
276
+
277
+ const bulb = this.device as VeSyncBulb;
278
+
279
+ if (!bulb.setColorTemperature) {
280
+ throw new Error('Device does not support color temperature');
281
+ }
282
+
283
+ const success = await bulb.setColorTemperature(value as number);
284
+
208
285
  if (!success) {
209
286
  throw new Error(`Failed to set color temperature to ${value}`);
210
287
  }
211
-
288
+
212
289
  await this.persistDeviceState('colorTemp', value);
213
290
  } catch (error) {
214
291
  this.handleDeviceError('set color temperature', error);
@@ -216,24 +293,40 @@ export class LightAccessory extends BaseAccessory {
216
293
  }
217
294
 
218
295
  private async getHue(): Promise<CharacteristicValue> {
219
- return this.device.hue || 0;
296
+ if (this.isDimmerDevice) {
297
+ return 0;
298
+ }
299
+
300
+ const bulb = this.device as VeSyncBulb;
301
+ return bulb.hue || 0;
220
302
  }
221
303
 
222
304
  private async getSaturation(): Promise<CharacteristicValue> {
223
- return this.device.saturation || 0;
305
+ if (this.isDimmerDevice) {
306
+ return 0;
307
+ }
308
+
309
+ const bulb = this.device as VeSyncBulb;
310
+ return bulb.saturation || 0;
224
311
  }
225
312
 
226
313
  private async setHue(value: CharacteristicValue): Promise<void> {
227
314
  try {
228
- if (!this.device.setColor) {
315
+ if (this.isDimmerDevice) {
229
316
  throw new Error('Device does not support color');
230
317
  }
231
-
232
- const success = await this.device.setColor(
318
+
319
+ const bulb = this.device as VeSyncBulb;
320
+
321
+ if (!bulb.setColor) {
322
+ throw new Error('Device does not support color');
323
+ }
324
+
325
+ const success = await bulb.setColor(
233
326
  value as number,
234
- this.device.saturation || 0
327
+ bulb.saturation || 0
235
328
  );
236
-
329
+
237
330
  if (!success) {
238
331
  throw new Error(`Failed to set hue to ${value}`);
239
332
  }
@@ -246,22 +339,310 @@ export class LightAccessory extends BaseAccessory {
246
339
 
247
340
  private async setSaturation(value: CharacteristicValue): Promise<void> {
248
341
  try {
249
- if (!this.device.setColor) {
342
+ if (this.isDimmerDevice) {
250
343
  throw new Error('Device does not support color');
251
344
  }
252
-
253
- const success = await this.device.setColor(
254
- this.device.hue || 0,
345
+
346
+ const bulb = this.device as VeSyncBulb;
347
+
348
+ if (!bulb.setColor) {
349
+ throw new Error('Device does not support color');
350
+ }
351
+
352
+ const success = await bulb.setColor(
353
+ bulb.hue || 0,
255
354
  value as number
256
355
  );
257
-
356
+
258
357
  if (!success) {
259
358
  throw new Error(`Failed to set saturation to ${value}`);
260
359
  }
261
-
360
+
262
361
  await this.persistDeviceState('saturation', value);
263
362
  } catch (error) {
264
363
  this.handleDeviceError('set saturation', error);
265
364
  }
266
365
  }
267
- }
366
+
367
+ private detectDimmer(device: VeSyncLightDevice): device is VeSyncDimmerSwitch {
368
+ return typeof (device as VeSyncDimmerSwitch).rgbColorSet === 'function';
369
+ }
370
+
371
+ private setupIndicatorService(): void {
372
+ const serviceName = `${this.device.deviceName} Indicator`;
373
+ const existing = this.accessory.getService(serviceName);
374
+ this.indicatorService = existing || this.accessory.addService(
375
+ this.platform.Service.Lightbulb,
376
+ serviceName,
377
+ 'indicator'
378
+ );
379
+
380
+ const indicatorService = this.indicatorService;
381
+ if (!indicatorService) {
382
+ return;
383
+ }
384
+
385
+ this.setupCharacteristicForService(
386
+ indicatorService,
387
+ this.platform.Characteristic.On,
388
+ this.getIndicatorOn.bind(this),
389
+ this.setIndicatorOn.bind(this)
390
+ );
391
+
392
+ this.setupCharacteristicForService(
393
+ indicatorService,
394
+ this.platform.Characteristic.Hue,
395
+ this.getIndicatorHue.bind(this),
396
+ this.setIndicatorHue.bind(this)
397
+ );
398
+
399
+ this.setupCharacteristicForService(
400
+ indicatorService,
401
+ this.platform.Characteristic.Saturation,
402
+ this.getIndicatorSaturation.bind(this),
403
+ this.setIndicatorSaturation.bind(this)
404
+ );
405
+ }
406
+
407
+ private setupCharacteristicForService(
408
+ service: Service,
409
+ characteristic: any,
410
+ onGet?: () => Promise<CharacteristicValue>,
411
+ onSet?: (value: CharacteristicValue) => Promise<void>
412
+ ): void {
413
+ this.withService(service, () => {
414
+ this.setupCharacteristic(characteristic, onGet, onSet);
415
+ });
416
+ }
417
+
418
+ private updateIndicatorCharacteristic(
419
+ characteristic: any,
420
+ value: CharacteristicValue
421
+ ): void {
422
+ if (!this.indicatorService) {
423
+ return;
424
+ }
425
+
426
+ this.withService(this.indicatorService, () => {
427
+ this.updateCharacteristicValue(characteristic, value);
428
+ });
429
+ }
430
+
431
+ private withService<T>(service: Service, fn: () => T): T {
432
+ const currentService = this.service;
433
+ this.service = service;
434
+ try {
435
+ return fn();
436
+ } finally {
437
+ this.service = currentService;
438
+ }
439
+ }
440
+
441
+ private async getIndicatorOn(): Promise<CharacteristicValue> {
442
+ if (!this.isDimmerDevice) {
443
+ return false;
444
+ }
445
+ const dimmer = this.device as VeSyncDimmerSwitch;
446
+ return dimmer.rgbLightStatus === 'on';
447
+ }
448
+
449
+ private async setIndicatorOn(value: CharacteristicValue): Promise<void> {
450
+ if (!this.isDimmerDevice) {
451
+ return;
452
+ }
453
+
454
+ const isOn = Boolean(value);
455
+ const dimmer = this.device as VeSyncDimmerSwitch;
456
+ const success = isOn ? await dimmer.rgbColorOn() : await dimmer.rgbColorOff();
457
+
458
+ if (!success) {
459
+ throw new Error(`Failed to turn ${isOn ? 'on' : 'off'} indicator light`);
460
+ }
461
+
462
+ await this.refreshDimmerDetails();
463
+ }
464
+
465
+ private async getIndicatorHue(): Promise<CharacteristicValue> {
466
+ if (!this.isDimmerDevice) {
467
+ return 0;
468
+ }
469
+
470
+ const { hue } = this.rgbToHsv(
471
+ (this.device as VeSyncDimmerSwitch).rgbLightValue.red,
472
+ (this.device as VeSyncDimmerSwitch).rgbLightValue.green,
473
+ (this.device as VeSyncDimmerSwitch).rgbLightValue.blue
474
+ );
475
+ this.indicatorColorState.hue = hue;
476
+ return hue;
477
+ }
478
+
479
+ private async setIndicatorHue(value: CharacteristicValue): Promise<void> {
480
+ if (!this.isDimmerDevice) {
481
+ return;
482
+ }
483
+
484
+ this.indicatorColorState.hue = Number(value);
485
+ this.scheduleIndicatorColorUpdate();
486
+ }
487
+
488
+ private async getIndicatorSaturation(): Promise<CharacteristicValue> {
489
+ if (!this.isDimmerDevice) {
490
+ return 0;
491
+ }
492
+
493
+ const { saturation } = this.rgbToHsv(
494
+ (this.device as VeSyncDimmerSwitch).rgbLightValue.red,
495
+ (this.device as VeSyncDimmerSwitch).rgbLightValue.green,
496
+ (this.device as VeSyncDimmerSwitch).rgbLightValue.blue
497
+ );
498
+ this.indicatorColorState.saturation = saturation;
499
+ return saturation;
500
+ }
501
+
502
+ private async setIndicatorSaturation(value: CharacteristicValue): Promise<void> {
503
+ if (!this.isDimmerDevice) {
504
+ return;
505
+ }
506
+
507
+ this.indicatorColorState.saturation = Number(value);
508
+ this.scheduleIndicatorColorUpdate();
509
+ }
510
+
511
+ private scheduleIndicatorColorUpdate(): void {
512
+ if (this.indicatorColorUpdateTimeout) {
513
+ clearTimeout(this.indicatorColorUpdateTimeout);
514
+ }
515
+
516
+ this.indicatorColorUpdateTimeout = setTimeout(() => {
517
+ void this.pushIndicatorColor().catch(error => {
518
+ void this.handleDeviceError('set indicator color', error);
519
+ });
520
+ }, 250);
521
+ }
522
+
523
+ private async pushIndicatorColor(): Promise<void> {
524
+ if (!this.isDimmerDevice) {
525
+ return;
526
+ }
527
+
528
+ const [red, green, blue] = this.hsvToRgb(
529
+ this.indicatorColorState.hue,
530
+ this.indicatorColorState.saturation,
531
+ 100
532
+ );
533
+
534
+ const dimmer = this.device as VeSyncDimmerSwitch;
535
+ const success = await dimmer.rgbColorSet(red, green, blue);
536
+
537
+ if (!success) {
538
+ throw new Error('Failed to update indicator color');
539
+ }
540
+
541
+ this.indicatorColorState = {
542
+ hue: this.indicatorColorState.hue,
543
+ saturation: this.indicatorColorState.saturation,
544
+ };
545
+
546
+ await this.refreshDimmerDetails();
547
+ }
548
+
549
+ private hsvToRgb(h: number, s: number, v: number): [number, number, number] {
550
+ let hue = h % 360;
551
+ if (hue < 0) {
552
+ hue += 360;
553
+ }
554
+ const saturation = Math.max(0, Math.min(100, s)) / 100;
555
+ const value = Math.max(0, Math.min(100, v)) / 100;
556
+
557
+ const c = value * saturation;
558
+ const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
559
+ const m = value - c;
560
+
561
+ let rPrime = 0;
562
+ let gPrime = 0;
563
+ let bPrime = 0;
564
+
565
+ if (hue < 60) {
566
+ rPrime = c;
567
+ gPrime = x;
568
+ } else if (hue < 120) {
569
+ rPrime = x;
570
+ gPrime = c;
571
+ } else if (hue < 180) {
572
+ gPrime = c;
573
+ bPrime = x;
574
+ } else if (hue < 240) {
575
+ gPrime = x;
576
+ bPrime = c;
577
+ } else if (hue < 300) {
578
+ rPrime = x;
579
+ bPrime = c;
580
+ } else {
581
+ rPrime = c;
582
+ bPrime = x;
583
+ }
584
+
585
+ const red = Math.round((rPrime + m) * 255);
586
+ const green = Math.round((gPrime + m) * 255);
587
+ const blue = Math.round((bPrime + m) * 255);
588
+
589
+ return [red, green, blue];
590
+ }
591
+
592
+ private rgbToHsv(r: number, g: number, b: number): { hue: number; saturation: number; value: number } {
593
+ const red = r / 255;
594
+ const green = g / 255;
595
+ const blue = b / 255;
596
+
597
+ const max = Math.max(red, green, blue);
598
+ const min = Math.min(red, green, blue);
599
+ const delta = max - min;
600
+
601
+ let hue = 0;
602
+ if (delta !== 0) {
603
+ if (max === red) {
604
+ hue = ((green - blue) / delta) % 6;
605
+ } else if (max === green) {
606
+ hue = (blue - red) / delta + 2;
607
+ } else {
608
+ hue = (red - green) / delta + 4;
609
+ }
610
+ hue *= 60;
611
+ if (hue < 0) {
612
+ hue += 360;
613
+ }
614
+ }
615
+
616
+ const saturation = max === 0 ? 0 : (delta / max) * 100;
617
+ const value = max * 100;
618
+
619
+ return {
620
+ hue: Math.round(hue),
621
+ saturation: Math.round(saturation),
622
+ value: Math.round(value),
623
+ };
624
+ }
625
+
626
+ private async refreshDimmerDetails(): Promise<void> {
627
+ if (!this.isDimmerDevice) {
628
+ return;
629
+ }
630
+
631
+ const dimmer = this.device as VeSyncDimmerSwitch;
632
+ try {
633
+ const now = Date.now();
634
+ if (
635
+ now - this.lastDimmerRefresh >= LightAccessory.DIMMER_REFRESH_DEBOUNCE_MS &&
636
+ typeof (dimmer as any).getDetails === 'function'
637
+ ) {
638
+ await (dimmer as any).getDetails();
639
+ this.lastDimmerRefresh = now;
640
+ }
641
+ } catch (error) {
642
+ this.platform.log.debug(
643
+ `${this.device.deviceName}: failed to refresh details after state change`,
644
+ error instanceof Error ? error : new Error(String(error))
645
+ );
646
+ }
647
+ }
648
+ }
package/src/platform.ts CHANGED
@@ -344,7 +344,27 @@ export class TSVESyncPlatform implements DynamicPlatformPlugin {
344
344
  * Update device states periodically
345
345
  */
346
346
  private async updateDeviceStates() {
347
- await this.discoverDevices();
347
+ if (this.refreshInProgress) {
348
+ return;
349
+ }
350
+
351
+ this.refreshInProgress = true;
352
+
353
+ try {
354
+ await this.discoverDevices();
355
+
356
+ const syncTasks = Array.from(this.deviceAccessories.values()).map(async accessory => {
357
+ try {
358
+ await accessory.syncDeviceState();
359
+ } catch (error) {
360
+ this.logger.warn('Failed to sync device state during scheduled refresh', error as Error);
361
+ }
362
+ });
363
+
364
+ await Promise.all(syncTasks);
365
+ } finally {
366
+ this.refreshInProgress = false;
367
+ }
348
368
  }
349
369
 
350
370
  /**
@@ -22,6 +22,7 @@ export interface VeSyncDeviceWithPower extends VeSyncBaseDevice {
22
22
  deviceStatus: string;
23
23
  turnOn(): Promise<boolean>;
24
24
  turnOff(): Promise<boolean>;
25
+ getDetails?: (...args: any[]) => any;
25
26
  }
26
27
 
27
28
  export interface VeSyncDeviceWithBrightness extends VeSyncDeviceWithPower {
@@ -84,8 +85,25 @@ export interface VeSyncFan extends VeSyncDeviceWithPower, VeSyncDeviceWithSpeed,
84
85
  setMode(mode: 'normal' | 'auto' | 'sleep' | 'turbo'): Promise<boolean>;
85
86
  }
86
87
 
88
+ export interface VeSyncDimmerSwitch extends VeSyncDeviceWithBrightness {
89
+ rgbLightStatus: string;
90
+ indicatorLightStatus: string;
91
+ rgbLightValue: {
92
+ red: number;
93
+ green: number;
94
+ blue: number;
95
+ };
96
+ rgbColorSet(red: number, green: number, blue: number): Promise<boolean>;
97
+ rgbColorOn(): Promise<boolean>;
98
+ rgbColorOff(): Promise<boolean>;
99
+ indicatorLightOn(): Promise<boolean>;
100
+ indicatorLightOff(): Promise<boolean>;
101
+ }
102
+
87
103
  export interface VeSyncBulb extends VeSyncDeviceWithPower, VeSyncDeviceWithBrightness, VeSyncDeviceWithColor {}
88
104
 
105
+ export type VeSyncLightDevice = VeSyncBulb | VeSyncDimmerSwitch;
106
+
89
107
  export interface VeSyncOutlet extends VeSyncDeviceWithPower {
90
108
  power?: number;
91
109
  voltage?: number;
@@ -9,11 +9,12 @@ import { OutletAccessory } from '../accessories/outlet.accessory';
9
9
  import { SwitchAccessory } from '../accessories/switch.accessory';
10
10
  import { BaseAccessory } from '../accessories/base.accessory';
11
11
  import { AirQualitySensorAccessory } from '../accessories/air-quality-sensor.accessory';
12
- import {
12
+ import {
13
13
  VeSyncAirPurifier,
14
14
  VeSyncHumidifier,
15
15
  VeSyncFan,
16
16
  VeSyncBulb,
17
+ VeSyncDimmerSwitch,
17
18
  VeSyncOutlet,
18
19
  VeSyncSwitch
19
20
  } from '../types/device.types';
@@ -38,7 +39,9 @@ const OUTLET_MODELS = [
38
39
  'ESW10-USA', 'wifi-switch-1.3'
39
40
  ];
40
41
 
41
- const SWITCH_MODELS = ['ESWD16', 'ESWL01', 'ESWL03'];
42
+ const SWITCH_MODELS = ['ESWL01', 'ESWL03'];
43
+
44
+ const DIMMER_MODELS = ['ESWD16'];
42
45
 
43
46
  export class DeviceFactory {
44
47
  private static modelMatches(deviceType: string, models: string[]): boolean {
@@ -66,6 +69,10 @@ export class DeviceFactory {
66
69
  return deviceType.startsWith('ESL') || deviceType === 'XYD0001';
67
70
  }
68
71
 
72
+ private static isDimmer(deviceType: string): boolean {
73
+ return this.modelMatches(deviceType, DIMMER_MODELS);
74
+ }
75
+
69
76
  private static isOutlet(deviceType: string): boolean {
70
77
  return this.modelMatches(deviceType, OUTLET_MODELS);
71
78
  }
@@ -96,6 +103,11 @@ export class DeviceFactory {
96
103
  return new FanAccessory(platform, accessory, device as VeSyncFan);
97
104
  }
98
105
 
106
+ // Dimmers
107
+ if (this.isDimmer(deviceType)) {
108
+ return new LightAccessory(platform, accessory, device as VeSyncDimmerSwitch);
109
+ }
110
+
99
111
  // Bulbs
100
112
  if (this.isBulb(deviceType)) {
101
113
  return new LightAccessory(platform, accessory, device as VeSyncBulb);
@@ -131,7 +143,7 @@ export class DeviceFactory {
131
143
  return Categories.FAN;
132
144
  }
133
145
 
134
- if (this.isBulb(type)) {
146
+ if (this.isDimmer(type) || this.isBulb(type)) {
135
147
  return Categories.LIGHTBULB;
136
148
  }
137
149
 
@@ -179,4 +191,4 @@ export class DeviceFactory {
179
191
  platform.log.debug(`Not creating AQ sensor for ${device.deviceName} - no air quality support detected`);
180
192
  return null;
181
193
  }
182
- }
194
+ }