homebridge-cync-app 0.1.6 → 0.1.7
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/.github/ISSUE_TEMPLATE/add-support-for-a-new-cync-device.md +139 -0
- package/CHANGELOG.md +8 -0
- package/README.md +3 -1
- package/dist/cync/config-client.d.ts +12 -0
- package/dist/cync/config-client.js +55 -0
- package/dist/cync/config-client.js.map +1 -1
- package/dist/cync/cync-client.d.ts +4 -0
- package/dist/cync/cync-client.js +79 -2
- package/dist/cync/cync-client.js.map +1 -1
- package/dist/cync/device-catalog.d.ts +19 -0
- package/dist/cync/device-catalog.js +35 -0
- package/dist/cync/device-catalog.js.map +1 -0
- package/dist/platform.d.ts +7 -0
- package/dist/platform.js +139 -46
- package/dist/platform.js.map +1 -1
- package/package.json +1 -1
- package/src/cync/config-client.ts +79 -0
- package/src/cync/cync-client.ts +116 -2
- package/src/cync/device-catalog.ts +55 -0
- package/src/platform.ts +213 -63
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/cync/device-catalog.ts
|
|
2
|
+
|
|
3
|
+
import type { Categories } from 'homebridge';
|
|
4
|
+
|
|
5
|
+
export interface CyncDeviceModel {
|
|
6
|
+
/** Raw deviceType from the Cync API */
|
|
7
|
+
deviceType: number;
|
|
8
|
+
|
|
9
|
+
/** Model name as shown in the Cync app (what you want HomeKit to show) */
|
|
10
|
+
modelName: string;
|
|
11
|
+
|
|
12
|
+
/** Optional marketing / retail name if you want to surface it somewhere else */
|
|
13
|
+
marketingName?: string;
|
|
14
|
+
|
|
15
|
+
/** Optional suggested HomeKit category override */
|
|
16
|
+
defaultCategory?: Categories;
|
|
17
|
+
|
|
18
|
+
/** Free-form notes for you / debugging */
|
|
19
|
+
notes?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Device catalog keyed by deviceType.
|
|
24
|
+
* Extend this as you discover more types.
|
|
25
|
+
*/
|
|
26
|
+
export const DEVICE_CATALOG: Record<number, CyncDeviceModel> = {
|
|
27
|
+
46: {
|
|
28
|
+
deviceType: 46,
|
|
29
|
+
modelName: '6" Recessed Can Retrofit Fixture (Matter)',
|
|
30
|
+
marketingName: 'Cync reveal HD+',
|
|
31
|
+
// defaultCategory: Categories.LIGHTBULB,
|
|
32
|
+
},
|
|
33
|
+
64: {
|
|
34
|
+
deviceType: 64,
|
|
35
|
+
modelName: 'Indoor Smart Plug',
|
|
36
|
+
marketingName: 'On/Off Smart Plug',
|
|
37
|
+
// defaultCategory: Categories.OUTLET,
|
|
38
|
+
},
|
|
39
|
+
65: {
|
|
40
|
+
deviceType: 65,
|
|
41
|
+
modelName: 'Indoor Smart Plug',
|
|
42
|
+
marketingName: 'Cync Indoor Plug',
|
|
43
|
+
// defaultCategory: Categories.OUTLET,
|
|
44
|
+
},
|
|
45
|
+
172: {
|
|
46
|
+
deviceType: 172,
|
|
47
|
+
modelName: 'Indoor Smart Plug (3in1)',
|
|
48
|
+
marketingName: 'Cync Indoor Smart Plug',
|
|
49
|
+
// defaultCategory: Categories.OUTLET,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function lookupDeviceModel(deviceType: number): CyncDeviceModel | undefined {
|
|
54
|
+
return DEVICE_CATALOG[deviceType];
|
|
55
|
+
}
|
package/src/platform.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { ConfigClient } from './cync/config-client.js';
|
|
|
13
13
|
import type { CyncCloudConfig, CyncDevice, CyncDeviceMesh } from './cync/config-client.js';
|
|
14
14
|
import { TcpClient } from './cync/tcp-client.js';
|
|
15
15
|
import type { CyncLogger } from './cync/config-client.js';
|
|
16
|
+
import { lookupDeviceModel } from './cync/device-catalog.js';
|
|
16
17
|
|
|
17
18
|
// Narrowed view of the Cync device properties returned by getDeviceProperties()
|
|
18
19
|
type CyncDeviceRaw = {
|
|
@@ -118,6 +119,31 @@ function hsvToRgb(hue: number, saturation: number, value: number): { r: number;
|
|
|
118
119
|
};
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
function resolveDeviceType(device: CyncDevice): number | undefined {
|
|
123
|
+
const typedDevice = device as unknown as {
|
|
124
|
+
device_type?: number;
|
|
125
|
+
raw?: { deviceType?: number | string };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (typeof typedDevice.device_type === 'number') {
|
|
129
|
+
return typedDevice.device_type;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const rawType = typedDevice.raw?.deviceType;
|
|
133
|
+
if (typeof rawType === 'number') {
|
|
134
|
+
return rawType;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (typeof rawType === 'string' && rawType.trim() !== '') {
|
|
138
|
+
const parsed = Number(rawType.trim());
|
|
139
|
+
if (!Number.isNaN(parsed)) {
|
|
140
|
+
return parsed;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
121
147
|
export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
122
148
|
public readonly accessories: PlatformAccessory[] = [];
|
|
123
149
|
public configureAccessory(accessory: PlatformAccessory): void {
|
|
@@ -132,6 +158,43 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
132
158
|
|
|
133
159
|
private cloudConfig: CyncCloudConfig | null = null;
|
|
134
160
|
private readonly deviceIdToAccessory = new Map<string, PlatformAccessory>();
|
|
161
|
+
private readonly deviceLastSeen = new Map<string, number>();
|
|
162
|
+
private readonly devicePollTimers = new Map<string, NodeJS.Timeout>();
|
|
163
|
+
|
|
164
|
+
private readonly offlineTimeoutMs = 5 * 60 * 1000; // 5 minutes
|
|
165
|
+
private readonly pollIntervalMs = 60_000; // 60 seconds
|
|
166
|
+
|
|
167
|
+
private markDeviceSeen(deviceId: string): void {
|
|
168
|
+
this.deviceLastSeen.set(deviceId, Date.now());
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private isDeviceProbablyOffline(deviceId: string): boolean {
|
|
172
|
+
const last = this.deviceLastSeen.get(deviceId);
|
|
173
|
+
if (!last) {
|
|
174
|
+
// No data yet; treat as online until we know better
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return Date.now() - last > this.offlineTimeoutMs;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private startPollingDevice(deviceId: string): void {
|
|
181
|
+
// For now this is just a placeholder hook. We keep a timer per device so
|
|
182
|
+
// you can later add a real poll (e.g. TCP “ping” or cloud get) here if you want.
|
|
183
|
+
const existing = this.devicePollTimers.get(deviceId);
|
|
184
|
+
if (existing) {
|
|
185
|
+
clearInterval(existing);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const timer = setInterval(() => {
|
|
189
|
+
// Optional future hook:
|
|
190
|
+
// - Call a "getDeviceState" or similar on tcpClient/client
|
|
191
|
+
// - On success, call this.markDeviceSeen(deviceId)
|
|
192
|
+
// - On failure, optionally log or mark offline
|
|
193
|
+
}, this.pollIntervalMs);
|
|
194
|
+
|
|
195
|
+
this.devicePollTimers.set(deviceId, timer);
|
|
196
|
+
}
|
|
197
|
+
|
|
135
198
|
private handleLanUpdate(update: unknown): void {
|
|
136
199
|
// Parsed 0x83 frames from TcpClient.parseLanSwitchUpdate look like:
|
|
137
200
|
// { controllerId: number, deviceId?: string, on: boolean, level: number }
|
|
@@ -146,6 +209,7 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
146
209
|
}
|
|
147
210
|
|
|
148
211
|
const accessory = this.deviceIdToAccessory.get(payload.deviceId);
|
|
212
|
+
this.markDeviceSeen(payload.deviceId);
|
|
149
213
|
if (!accessory) {
|
|
150
214
|
this.log.debug(
|
|
151
215
|
'Cync: LAN update for unknown deviceId=%s; no accessory mapping',
|
|
@@ -235,6 +299,8 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
235
299
|
);
|
|
236
300
|
accessory.removeService(existingLight);
|
|
237
301
|
}
|
|
302
|
+
this.applyAccessoryInformationFromCyncDevice(accessory, device, deviceName, deviceId);
|
|
303
|
+
|
|
238
304
|
// Ensure context is initialized
|
|
239
305
|
const ctx = accessory.context as CyncAccessoryContext;
|
|
240
306
|
ctx.cync = ctx.cync ?? {
|
|
@@ -246,10 +312,18 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
246
312
|
|
|
247
313
|
// Remember mapping for LAN updates
|
|
248
314
|
this.deviceIdToAccessory.set(deviceId, accessory);
|
|
315
|
+
this.markDeviceSeen(deviceId);
|
|
316
|
+
this.startPollingDevice(deviceId);
|
|
249
317
|
|
|
250
318
|
service
|
|
251
319
|
.getCharacteristic(this.api.hap.Characteristic.On)
|
|
252
320
|
.onGet(() => {
|
|
321
|
+
if (this.isDeviceProbablyOffline(deviceId)) {
|
|
322
|
+
throw new this.api.hap.HapStatusError(
|
|
323
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
253
327
|
const currentOn = !!ctx.cync?.on;
|
|
254
328
|
this.log.info(
|
|
255
329
|
'Cync: On.get -> %s for %s (deviceId=%s)',
|
|
@@ -339,6 +413,8 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
339
413
|
|
|
340
414
|
// Remember mapping for LAN updates
|
|
341
415
|
this.deviceIdToAccessory.set(deviceId, accessory);
|
|
416
|
+
this.markDeviceSeen(deviceId);
|
|
417
|
+
this.startPollingDevice(deviceId);
|
|
342
418
|
|
|
343
419
|
const Characteristic = this.api.hap.Characteristic;
|
|
344
420
|
|
|
@@ -346,6 +422,12 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
346
422
|
service
|
|
347
423
|
.getCharacteristic(Characteristic.On)
|
|
348
424
|
.onGet(() => {
|
|
425
|
+
if (this.isDeviceProbablyOffline(deviceId)) {
|
|
426
|
+
throw new this.api.hap.HapStatusError(
|
|
427
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
349
431
|
const currentOn = !!ctx.cync?.on;
|
|
350
432
|
this.log.info(
|
|
351
433
|
'Cync: Light On.get -> %s for %s (deviceId=%s)',
|
|
@@ -378,13 +460,32 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
378
460
|
// Optimistic local cache; LAN update will confirm
|
|
379
461
|
cyncMeta.on = on;
|
|
380
462
|
|
|
381
|
-
|
|
382
|
-
|
|
463
|
+
try {
|
|
464
|
+
await this.tcpClient.setSwitchState(cyncMeta.deviceId, { on });
|
|
465
|
+
this.markDeviceSeen(cyncMeta.deviceId);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
this.log.warn(
|
|
468
|
+
'Cync: Light On.set failed for %s (deviceId=%s): %s',
|
|
469
|
+
deviceName,
|
|
470
|
+
cyncMeta.deviceId,
|
|
471
|
+
(err as Error).message ?? String(err),
|
|
472
|
+
);
|
|
383
473
|
|
|
474
|
+
throw new this.api.hap.HapStatusError(
|
|
475
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
384
479
|
// ----- Brightness (dimming via LAN combo_control) -----
|
|
385
480
|
service
|
|
386
481
|
.getCharacteristic(Characteristic.Brightness)
|
|
387
482
|
.onGet(() => {
|
|
483
|
+
if (this.isDeviceProbablyOffline(deviceId)) {
|
|
484
|
+
throw new this.api.hap.HapStatusError(
|
|
485
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
388
489
|
const current = ctx.cync?.brightness;
|
|
389
490
|
|
|
390
491
|
// If we have a cached LAN level, use it; otherwise infer from On.
|
|
@@ -406,10 +507,7 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
406
507
|
return;
|
|
407
508
|
}
|
|
408
509
|
|
|
409
|
-
const brightness = Math.max(
|
|
410
|
-
0,
|
|
411
|
-
Math.min(100, Number(value)),
|
|
412
|
-
);
|
|
510
|
+
const brightness = Math.max(0, Math.min(100, Number(value)));
|
|
413
511
|
|
|
414
512
|
if (!Number.isFinite(brightness)) {
|
|
415
513
|
this.log.warn(
|
|
@@ -432,18 +530,43 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
432
530
|
cyncMeta.deviceId,
|
|
433
531
|
);
|
|
434
532
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
533
|
+
try {
|
|
534
|
+
// If we're in "color mode", keep the existing RGB and scale brightness via setColor();
|
|
535
|
+
// otherwise treat this as a white-brightness change.
|
|
536
|
+
if (cyncMeta.colorActive && cyncMeta.rgb) {
|
|
537
|
+
await this.tcpClient.setColor(
|
|
538
|
+
cyncMeta.deviceId,
|
|
539
|
+
cyncMeta.rgb,
|
|
540
|
+
brightness,
|
|
541
|
+
);
|
|
542
|
+
} else {
|
|
543
|
+
await this.tcpClient.setBrightness(cyncMeta.deviceId, brightness);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
this.markDeviceSeen(cyncMeta.deviceId);
|
|
547
|
+
} catch (err) {
|
|
548
|
+
this.log.warn(
|
|
549
|
+
'Cync: Light Brightness.set failed for %s (deviceId=%s): %s',
|
|
550
|
+
deviceName,
|
|
551
|
+
cyncMeta.deviceId,
|
|
552
|
+
(err as Error).message ?? String(err),
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
throw new this.api.hap.HapStatusError(
|
|
556
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
557
|
+
);
|
|
441
558
|
}
|
|
442
559
|
});
|
|
443
560
|
// ----- Hue -----
|
|
444
561
|
service
|
|
445
562
|
.getCharacteristic(Characteristic.Hue)
|
|
446
563
|
.onGet(() => {
|
|
564
|
+
if (this.isDeviceProbablyOffline(deviceId)) {
|
|
565
|
+
throw new this.api.hap.HapStatusError(
|
|
566
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
447
570
|
const hue = ctx.cync?.hue;
|
|
448
571
|
if (typeof hue === 'number') {
|
|
449
572
|
return hue;
|
|
@@ -474,13 +597,11 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
474
597
|
}
|
|
475
598
|
|
|
476
599
|
// Use cached saturation/brightness if available, otherwise sane defaults
|
|
477
|
-
const saturation =
|
|
478
|
-
? cyncMeta.saturation
|
|
479
|
-
: 100;
|
|
600
|
+
const saturation =
|
|
601
|
+
typeof cyncMeta.saturation === 'number' ? cyncMeta.saturation : 100;
|
|
480
602
|
|
|
481
|
-
const brightness =
|
|
482
|
-
? cyncMeta.brightness
|
|
483
|
-
: 100;
|
|
603
|
+
const brightness =
|
|
604
|
+
typeof cyncMeta.brightness === 'number' ? cyncMeta.brightness : 100;
|
|
484
605
|
|
|
485
606
|
const rgb = hsvToRgb(hue, saturation, brightness);
|
|
486
607
|
|
|
@@ -503,13 +624,32 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
503
624
|
brightness,
|
|
504
625
|
);
|
|
505
626
|
|
|
506
|
-
|
|
507
|
-
|
|
627
|
+
try {
|
|
628
|
+
await this.tcpClient.setColor(cyncMeta.deviceId, rgb, brightness);
|
|
629
|
+
this.markDeviceSeen(cyncMeta.deviceId);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
this.log.warn(
|
|
632
|
+
'Cync: Light Hue.set failed for %s (deviceId=%s): %s',
|
|
633
|
+
deviceName,
|
|
634
|
+
cyncMeta.deviceId,
|
|
635
|
+
(err as Error).message ?? String(err),
|
|
636
|
+
);
|
|
508
637
|
|
|
638
|
+
throw new this.api.hap.HapStatusError(
|
|
639
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
509
643
|
// ----- Saturation -----
|
|
510
644
|
service
|
|
511
645
|
.getCharacteristic(Characteristic.Saturation)
|
|
512
646
|
.onGet(() => {
|
|
647
|
+
if (this.isDeviceProbablyOffline(deviceId)) {
|
|
648
|
+
throw new this.api.hap.HapStatusError(
|
|
649
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
513
653
|
const sat = ctx.cync?.saturation;
|
|
514
654
|
if (typeof sat === 'number') {
|
|
515
655
|
return sat;
|
|
@@ -538,13 +678,10 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
538
678
|
return;
|
|
539
679
|
}
|
|
540
680
|
|
|
541
|
-
const hue = typeof cyncMeta.hue === 'number'
|
|
542
|
-
? cyncMeta.hue
|
|
543
|
-
: 0;
|
|
681
|
+
const hue = typeof cyncMeta.hue === 'number' ? cyncMeta.hue : 0;
|
|
544
682
|
|
|
545
|
-
const brightness =
|
|
546
|
-
? cyncMeta.brightness
|
|
547
|
-
: 100;
|
|
683
|
+
const brightness =
|
|
684
|
+
typeof cyncMeta.brightness === 'number' ? cyncMeta.brightness : 100;
|
|
548
685
|
|
|
549
686
|
const rgb = hsvToRgb(hue, saturation, brightness);
|
|
550
687
|
|
|
@@ -567,7 +704,21 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
567
704
|
brightness,
|
|
568
705
|
);
|
|
569
706
|
|
|
570
|
-
|
|
707
|
+
try {
|
|
708
|
+
await this.tcpClient.setColor(cyncMeta.deviceId, rgb, brightness);
|
|
709
|
+
this.markDeviceSeen(cyncMeta.deviceId);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
this.log.warn(
|
|
712
|
+
'Cync: Light Saturation.set failed for %s (deviceId=%s): %s',
|
|
713
|
+
deviceName,
|
|
714
|
+
cyncMeta.deviceId,
|
|
715
|
+
(err as Error).message ?? String(err),
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
throw new this.api.hap.HapStatusError(
|
|
719
|
+
this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
571
722
|
});
|
|
572
723
|
}
|
|
573
724
|
|
|
@@ -593,21 +744,42 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
593
744
|
// Manufacturer: fixed for all Cync devices
|
|
594
745
|
infoService.updateCharacteristic(Characteristic.Manufacturer, 'GE Lighting');
|
|
595
746
|
|
|
596
|
-
// Model:
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
747
|
+
// Model: prefer catalog entry (Cync app-style model name), fall back to raw info
|
|
748
|
+
const resolvedType = resolveDeviceType(device);
|
|
749
|
+
const catalogEntry = typeof resolvedType === 'number'
|
|
750
|
+
? lookupDeviceModel(resolvedType)
|
|
751
|
+
: undefined;
|
|
601
752
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
753
|
+
let model: string;
|
|
754
|
+
|
|
755
|
+
if (catalogEntry) {
|
|
756
|
+
// Use the Cync app-style model name
|
|
757
|
+
model = catalogEntry.modelName;
|
|
758
|
+
|
|
759
|
+
// Persist for debugging / future use
|
|
760
|
+
const ctx = accessory.context as Record<string, unknown>;
|
|
761
|
+
ctx.deviceType = resolvedType;
|
|
762
|
+
ctx.modelName = catalogEntry.modelName;
|
|
763
|
+
if (catalogEntry.marketingName) {
|
|
764
|
+
ctx.marketingName = catalogEntry.marketingName;
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
// Fallback: use device displayName + type
|
|
768
|
+
const modelBase =
|
|
769
|
+
typeof rawDevice.displayName === 'string' && rawDevice.displayName.trim().length > 0
|
|
770
|
+
? rawDevice.displayName.trim()
|
|
771
|
+
: 'Cync Device';
|
|
772
|
+
|
|
773
|
+
const modelSuffix =
|
|
774
|
+
typeof resolvedType === 'number'
|
|
775
|
+
? ` (Type ${resolvedType})`
|
|
776
|
+
: '';
|
|
777
|
+
|
|
778
|
+
model = modelBase + modelSuffix;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
infoService.updateCharacteristic(Characteristic.Model, model);
|
|
606
782
|
|
|
607
|
-
infoService.updateCharacteristic(
|
|
608
|
-
Characteristic.Model,
|
|
609
|
-
modelBase + modelSuffix,
|
|
610
|
-
);
|
|
611
783
|
|
|
612
784
|
// Serial: prefer wifiMac, then mac, then deviceID, then the string deviceId
|
|
613
785
|
const serial =
|
|
@@ -809,29 +981,7 @@ export class CyncAppPlatform implements DynamicPlatformPlugin {
|
|
|
809
981
|
this.accessories.push(accessory);
|
|
810
982
|
}
|
|
811
983
|
|
|
812
|
-
|
|
813
|
-
const typedDevice = device as unknown as {
|
|
814
|
-
device_type?: number;
|
|
815
|
-
raw?: { deviceType?: number | string };
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
let deviceType: number | undefined;
|
|
819
|
-
|
|
820
|
-
if (typeof typedDevice.device_type === 'number') {
|
|
821
|
-
deviceType = typedDevice.device_type;
|
|
822
|
-
} else if (typedDevice.raw && typeof typedDevice.raw.deviceType === 'number') {
|
|
823
|
-
deviceType = typedDevice.raw.deviceType;
|
|
824
|
-
} else if (
|
|
825
|
-
typedDevice.raw &&
|
|
826
|
-
typeof typedDevice.raw.deviceType === 'string' &&
|
|
827
|
-
typedDevice.raw.deviceType.trim() !== ''
|
|
828
|
-
) {
|
|
829
|
-
const parsed = Number(typedDevice.raw.deviceType);
|
|
830
|
-
if (!Number.isNaN(parsed)) {
|
|
831
|
-
deviceType = parsed;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
984
|
+
const deviceType = resolveDeviceType(device);
|
|
835
985
|
const isDownlight = deviceType === 46;
|
|
836
986
|
|
|
837
987
|
if (isDownlight) {
|