homebridge-cync-app 0.1.7 → 0.1.9
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/CHANGELOG.md +13 -0
- package/dist/cync/config-client.d.ts +11 -0
- package/dist/cync/config-client.js +113 -6
- package/dist/cync/config-client.js.map +1 -1
- package/dist/cync/cync-accessory-helpers.d.ts +46 -0
- package/dist/cync/cync-accessory-helpers.js +140 -0
- package/dist/cync/cync-accessory-helpers.js.map +1 -0
- package/dist/cync/cync-client.d.ts +4 -0
- package/dist/cync/cync-client.js +150 -34
- package/dist/cync/cync-client.js.map +1 -1
- package/dist/cync/cync-light-accessory.d.ts +4 -0
- package/dist/cync/cync-light-accessory.js +190 -0
- package/dist/cync/cync-light-accessory.js.map +1 -0
- package/dist/cync/cync-switch-accessory.d.ts +4 -0
- package/dist/cync/cync-switch-accessory.js +64 -0
- package/dist/cync/cync-switch-accessory.js.map +1 -0
- package/dist/cync/device-catalog.js +9 -4
- package/dist/cync/device-catalog.js.map +1 -1
- package/dist/cync/tcp-client.d.ts +7 -0
- package/dist/cync/tcp-client.js +122 -30
- package/dist/cync/tcp-client.js.map +1 -1
- package/dist/cync/token-store.js +2 -2
- package/dist/cync/token-store.js.map +1 -1
- package/dist/platform.d.ts +1 -3
- package/dist/platform.js +18 -382
- package/dist/platform.js.map +1 -1
- package/package.json +1 -1
- package/src/cync/config-client.ts +175 -12
- package/src/cync/cync-accessory-helpers.ts +233 -0
- package/src/cync/cync-client.ts +231 -44
- package/src/cync/cync-light-accessory.ts +369 -0
- package/src/cync/cync-switch-accessory.ts +119 -0
- package/src/cync/device-catalog.ts +9 -4
- package/src/cync/tcp-client.ts +153 -53
- package/src/cync/token-store.ts +3 -2
- package/src/platform.ts +49 -661
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// src/cync/cync-light-accessory.ts
|
|
2
|
+
import type { PlatformAccessory } from 'homebridge';
|
|
3
|
+
import type { CyncDevice, CyncDeviceMesh } from './config-client.js';
|
|
4
|
+
import type { CyncAccessoryContext, CyncAccessoryEnv } from './cync-accessory-helpers.js';
|
|
5
|
+
import { applyAccessoryInformationFromCyncDevice, hsvToRgb } from './cync-accessory-helpers.js';
|
|
6
|
+
|
|
7
|
+
export function configureCyncLightAccessory(
|
|
8
|
+
env: CyncAccessoryEnv,
|
|
9
|
+
mesh: CyncDeviceMesh,
|
|
10
|
+
device: CyncDevice,
|
|
11
|
+
accessory: PlatformAccessory,
|
|
12
|
+
deviceName: string,
|
|
13
|
+
deviceId: string,
|
|
14
|
+
): void {
|
|
15
|
+
// If this accessory used to be a switch, remove that service
|
|
16
|
+
const existingSwitch = accessory.getService(env.api.hap.Service.Switch);
|
|
17
|
+
if (existingSwitch) {
|
|
18
|
+
env.log.info(
|
|
19
|
+
'Cync: removing stale Switch service from %s (deviceId=%s) before configuring as Lightbulb',
|
|
20
|
+
deviceName,
|
|
21
|
+
deviceId,
|
|
22
|
+
);
|
|
23
|
+
accessory.removeService(existingSwitch);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const service =
|
|
27
|
+
accessory.getService(env.api.hap.Service.Lightbulb) ||
|
|
28
|
+
accessory.addService(env.api.hap.Service.Lightbulb, deviceName);
|
|
29
|
+
|
|
30
|
+
// Optionally update accessory category so UIs treat it as a light
|
|
31
|
+
if (accessory.category !== env.api.hap.Categories.LIGHTBULB) {
|
|
32
|
+
accessory.category = env.api.hap.Categories.LIGHTBULB;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Populate Accessory Information from Cync metadata
|
|
36
|
+
applyAccessoryInformationFromCyncDevice(env.api, accessory, device, deviceName, deviceId);
|
|
37
|
+
|
|
38
|
+
// Ensure context is initialized
|
|
39
|
+
const ctx = accessory.context as CyncAccessoryContext;
|
|
40
|
+
ctx.cync = ctx.cync ?? {
|
|
41
|
+
meshId: mesh.id,
|
|
42
|
+
deviceId,
|
|
43
|
+
productId: device.product_id,
|
|
44
|
+
on: false,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Remember mapping for LAN updates
|
|
48
|
+
env.registerAccessoryForDevice(deviceId, accessory);
|
|
49
|
+
env.markDeviceSeen(deviceId);
|
|
50
|
+
env.startPollingDevice(deviceId);
|
|
51
|
+
|
|
52
|
+
const Characteristic = env.api.hap.Characteristic;
|
|
53
|
+
|
|
54
|
+
// ----- On/Off -----
|
|
55
|
+
service
|
|
56
|
+
.getCharacteristic(Characteristic.On)
|
|
57
|
+
.onGet(() => {
|
|
58
|
+
const currentOn = !!ctx.cync?.on;
|
|
59
|
+
|
|
60
|
+
if (env.isDeviceProbablyOffline(deviceId)) {
|
|
61
|
+
env.log.debug(
|
|
62
|
+
'Cync: Light On.get offline-heuristic hit; returning cached=%s for %s (deviceId=%s)',
|
|
63
|
+
String(currentOn),
|
|
64
|
+
deviceName,
|
|
65
|
+
deviceId,
|
|
66
|
+
);
|
|
67
|
+
return currentOn;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
env.log.info(
|
|
71
|
+
'Cync: Light On.get -> %s for %s (deviceId=%s)',
|
|
72
|
+
String(currentOn),
|
|
73
|
+
deviceName,
|
|
74
|
+
deviceId,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return currentOn;
|
|
78
|
+
})
|
|
79
|
+
.onSet(async (value) => {
|
|
80
|
+
const cyncMeta = ctx.cync;
|
|
81
|
+
|
|
82
|
+
if (!cyncMeta?.deviceId) {
|
|
83
|
+
env.log.warn(
|
|
84
|
+
'Cync: Light On.set called for %s but no cync.deviceId in context',
|
|
85
|
+
deviceName,
|
|
86
|
+
);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const on = value === true || value === 1;
|
|
91
|
+
|
|
92
|
+
env.log.info(
|
|
93
|
+
'Cync: Light On.set -> %s for %s (deviceId=%s)',
|
|
94
|
+
String(on),
|
|
95
|
+
deviceName,
|
|
96
|
+
cyncMeta.deviceId,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Optimistic local cache; LAN update will confirm
|
|
100
|
+
cyncMeta.on = on;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await env.tcpClient.setSwitchState(cyncMeta.deviceId, { on });
|
|
104
|
+
env.markDeviceSeen(cyncMeta.deviceId);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
env.log.warn(
|
|
107
|
+
'Cync: Light On.set failed for %s (deviceId=%s): %s',
|
|
108
|
+
deviceName,
|
|
109
|
+
cyncMeta.deviceId,
|
|
110
|
+
(err as Error).message ?? String(err),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
throw new env.api.hap.HapStatusError(
|
|
114
|
+
env.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ----- Brightness (dimming via LAN combo_control) -----
|
|
120
|
+
service
|
|
121
|
+
.getCharacteristic(Characteristic.Brightness)
|
|
122
|
+
.onGet(() => {
|
|
123
|
+
const current = ctx.cync?.brightness;
|
|
124
|
+
|
|
125
|
+
const cachedBrightness =
|
|
126
|
+
typeof current === 'number'
|
|
127
|
+
? current
|
|
128
|
+
: (ctx.cync?.on ?? false) ? 100 : 0;
|
|
129
|
+
|
|
130
|
+
if (env.isDeviceProbablyOffline(deviceId)) {
|
|
131
|
+
env.log.debug(
|
|
132
|
+
'Cync: Light Brightness.get offline-heuristic hit; returning cached=%d for %s (deviceId=%s)',
|
|
133
|
+
cachedBrightness,
|
|
134
|
+
deviceName,
|
|
135
|
+
deviceId,
|
|
136
|
+
);
|
|
137
|
+
return cachedBrightness;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return cachedBrightness;
|
|
141
|
+
})
|
|
142
|
+
.onSet(async (value) => {
|
|
143
|
+
const cyncMeta = ctx.cync;
|
|
144
|
+
|
|
145
|
+
if (!cyncMeta?.deviceId) {
|
|
146
|
+
env.log.warn(
|
|
147
|
+
'Cync: Light Brightness.set called for %s but no cync.deviceId in context',
|
|
148
|
+
deviceName,
|
|
149
|
+
);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const brightness = Math.max(0, Math.min(100, Number(value)));
|
|
154
|
+
|
|
155
|
+
if (!Number.isFinite(brightness)) {
|
|
156
|
+
env.log.warn(
|
|
157
|
+
'Cync: Light Brightness.set received invalid value=%o for %s (deviceId=%s)',
|
|
158
|
+
value,
|
|
159
|
+
deviceName,
|
|
160
|
+
cyncMeta.deviceId,
|
|
161
|
+
);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Optimistic cache
|
|
166
|
+
cyncMeta.brightness = brightness;
|
|
167
|
+
cyncMeta.on = brightness > 0;
|
|
168
|
+
|
|
169
|
+
env.log.info(
|
|
170
|
+
'Cync: Light Brightness.set -> %d for %s (deviceId=%s)',
|
|
171
|
+
brightness,
|
|
172
|
+
deviceName,
|
|
173
|
+
cyncMeta.deviceId,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// If we're in "color mode", keep the existing RGB and scale brightness via setColor();
|
|
178
|
+
// otherwise treat this as a white-brightness change.
|
|
179
|
+
if (cyncMeta.colorActive && cyncMeta.rgb) {
|
|
180
|
+
await env.tcpClient.setColor(
|
|
181
|
+
cyncMeta.deviceId,
|
|
182
|
+
cyncMeta.rgb,
|
|
183
|
+
brightness,
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
await env.tcpClient.setBrightness(cyncMeta.deviceId, brightness);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
env.markDeviceSeen(cyncMeta.deviceId);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
env.log.warn(
|
|
192
|
+
'Cync: Light Brightness.set failed for %s (deviceId=%s): %s',
|
|
193
|
+
deviceName,
|
|
194
|
+
cyncMeta.deviceId,
|
|
195
|
+
(err as Error).message ?? String(err),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
throw new env.api.hap.HapStatusError(
|
|
199
|
+
env.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ----- Hue -----
|
|
205
|
+
service
|
|
206
|
+
.getCharacteristic(Characteristic.Hue)
|
|
207
|
+
.onGet(() => {
|
|
208
|
+
const hue = typeof ctx.cync?.hue === 'number' ? ctx.cync.hue : 0;
|
|
209
|
+
|
|
210
|
+
if (env.isDeviceProbablyOffline(deviceId)) {
|
|
211
|
+
env.log.debug(
|
|
212
|
+
'Cync: Light Hue.get offline-heuristic hit; returning cached=%d for %s (deviceId=%s)',
|
|
213
|
+
hue,
|
|
214
|
+
deviceName,
|
|
215
|
+
deviceId,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return hue;
|
|
220
|
+
})
|
|
221
|
+
.onSet(async (value) => {
|
|
222
|
+
const cyncMeta = ctx.cync;
|
|
223
|
+
|
|
224
|
+
if (!cyncMeta?.deviceId) {
|
|
225
|
+
env.log.warn(
|
|
226
|
+
'Cync: Light Hue.set called for %s but no cync.deviceId in context',
|
|
227
|
+
deviceName,
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const hue = Math.max(0, Math.min(360, Number(value)));
|
|
233
|
+
if (!Number.isFinite(hue)) {
|
|
234
|
+
env.log.warn(
|
|
235
|
+
'Cync: Light Hue.set received invalid value=%o for %s (deviceId=%s)',
|
|
236
|
+
value,
|
|
237
|
+
deviceName,
|
|
238
|
+
cyncMeta.deviceId,
|
|
239
|
+
);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Use cached saturation/brightness if available, otherwise sane defaults
|
|
244
|
+
const saturation =
|
|
245
|
+
typeof cyncMeta.saturation === 'number' ? cyncMeta.saturation : 100;
|
|
246
|
+
|
|
247
|
+
const brightness =
|
|
248
|
+
typeof cyncMeta.brightness === 'number' ? cyncMeta.brightness : 100;
|
|
249
|
+
|
|
250
|
+
const rgb = hsvToRgb(hue, saturation, brightness);
|
|
251
|
+
|
|
252
|
+
// Optimistic cache
|
|
253
|
+
cyncMeta.hue = hue;
|
|
254
|
+
cyncMeta.saturation = saturation;
|
|
255
|
+
cyncMeta.rgb = rgb;
|
|
256
|
+
cyncMeta.colorActive = true;
|
|
257
|
+
cyncMeta.on = brightness > 0;
|
|
258
|
+
cyncMeta.brightness = brightness;
|
|
259
|
+
|
|
260
|
+
env.log.info(
|
|
261
|
+
'Cync: Light Hue.set -> %d for %s (deviceId=%s) -> rgb=(%d,%d,%d) brightness=%d',
|
|
262
|
+
hue,
|
|
263
|
+
deviceName,
|
|
264
|
+
cyncMeta.deviceId,
|
|
265
|
+
rgb.r,
|
|
266
|
+
rgb.g,
|
|
267
|
+
rgb.b,
|
|
268
|
+
brightness,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
await env.tcpClient.setColor(cyncMeta.deviceId, rgb, brightness);
|
|
273
|
+
env.markDeviceSeen(cyncMeta.deviceId);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
env.log.warn(
|
|
276
|
+
'Cync: Light Hue.set failed for %s (deviceId=%s): %s',
|
|
277
|
+
deviceName,
|
|
278
|
+
cyncMeta.deviceId,
|
|
279
|
+
(err as Error).message ?? String(err),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
throw new env.api.hap.HapStatusError(
|
|
283
|
+
env.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// ----- Saturation -----
|
|
289
|
+
service
|
|
290
|
+
.getCharacteristic(Characteristic.Saturation)
|
|
291
|
+
.onGet(() => {
|
|
292
|
+
const sat = typeof ctx.cync?.saturation === 'number' ? ctx.cync.saturation : 100;
|
|
293
|
+
|
|
294
|
+
if (env.isDeviceProbablyOffline(deviceId)) {
|
|
295
|
+
env.log.debug(
|
|
296
|
+
'Cync: Light Saturation.get offline-heuristic hit; returning cached=%d for %s (deviceId=%s)',
|
|
297
|
+
sat,
|
|
298
|
+
deviceName,
|
|
299
|
+
deviceId,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return sat;
|
|
304
|
+
})
|
|
305
|
+
.onSet(async (value) => {
|
|
306
|
+
const cyncMeta = ctx.cync;
|
|
307
|
+
|
|
308
|
+
if (!cyncMeta?.deviceId) {
|
|
309
|
+
env.log.warn(
|
|
310
|
+
'Cync: Light Saturation.set called for %s but no cync.deviceId in context',
|
|
311
|
+
deviceName,
|
|
312
|
+
);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const saturation = Math.max(0, Math.min(100, Number(value)));
|
|
317
|
+
if (!Number.isFinite(saturation)) {
|
|
318
|
+
env.log.warn(
|
|
319
|
+
'Cync: Light Saturation.set received invalid value=%o for %s (deviceId=%s)',
|
|
320
|
+
value,
|
|
321
|
+
deviceName,
|
|
322
|
+
cyncMeta.deviceId,
|
|
323
|
+
);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const hue = typeof cyncMeta.hue === 'number' ? cyncMeta.hue : 0;
|
|
328
|
+
|
|
329
|
+
const brightness =
|
|
330
|
+
typeof cyncMeta.brightness === 'number' ? cyncMeta.brightness : 100;
|
|
331
|
+
|
|
332
|
+
const rgb = hsvToRgb(hue, saturation, brightness);
|
|
333
|
+
|
|
334
|
+
// Optimistic cache
|
|
335
|
+
cyncMeta.hue = hue;
|
|
336
|
+
cyncMeta.saturation = saturation;
|
|
337
|
+
cyncMeta.rgb = rgb;
|
|
338
|
+
cyncMeta.colorActive = true;
|
|
339
|
+
cyncMeta.on = brightness > 0;
|
|
340
|
+
cyncMeta.brightness = brightness;
|
|
341
|
+
|
|
342
|
+
env.log.info(
|
|
343
|
+
'Cync: Light Saturation.set -> %d for %s (deviceId=%s) -> rgb=(%d,%d,%d) brightness=%d',
|
|
344
|
+
saturation,
|
|
345
|
+
deviceName,
|
|
346
|
+
cyncMeta.deviceId,
|
|
347
|
+
rgb.r,
|
|
348
|
+
rgb.g,
|
|
349
|
+
rgb.b,
|
|
350
|
+
brightness,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
await env.tcpClient.setColor(cyncMeta.deviceId, rgb, brightness);
|
|
355
|
+
env.markDeviceSeen(cyncMeta.deviceId);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
env.log.warn(
|
|
358
|
+
'Cync: Light Saturation.set failed for %s (deviceId=%s): %s',
|
|
359
|
+
deviceName,
|
|
360
|
+
cyncMeta.deviceId,
|
|
361
|
+
(err as Error).message ?? String(err),
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
throw new env.api.hap.HapStatusError(
|
|
365
|
+
env.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/cync/cync-switch-accessory.ts
|
|
2
|
+
import type { PlatformAccessory } from 'homebridge';
|
|
3
|
+
import type { CyncDevice, CyncDeviceMesh } from './config-client.js';
|
|
4
|
+
import type { CyncAccessoryContext, CyncAccessoryEnv } from './cync-accessory-helpers.js';
|
|
5
|
+
import { applyAccessoryInformationFromCyncDevice } from './cync-accessory-helpers.js';
|
|
6
|
+
|
|
7
|
+
export function configureCyncSwitchAccessory(
|
|
8
|
+
env: CyncAccessoryEnv,
|
|
9
|
+
mesh: CyncDeviceMesh,
|
|
10
|
+
device: CyncDevice,
|
|
11
|
+
accessory: PlatformAccessory,
|
|
12
|
+
deviceName: string,
|
|
13
|
+
deviceId: string,
|
|
14
|
+
): void {
|
|
15
|
+
const service =
|
|
16
|
+
accessory.getService(env.api.hap.Service.Switch) ||
|
|
17
|
+
accessory.addService(env.api.hap.Service.Switch, deviceName);
|
|
18
|
+
|
|
19
|
+
const existingLight = accessory.getService(env.api.hap.Service.Lightbulb);
|
|
20
|
+
if (existingLight) {
|
|
21
|
+
env.log.info(
|
|
22
|
+
'Cync: removing stale Lightbulb service from %s (deviceId=%s) before configuring as Switch',
|
|
23
|
+
deviceName,
|
|
24
|
+
deviceId,
|
|
25
|
+
);
|
|
26
|
+
accessory.removeService(existingLight);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
applyAccessoryInformationFromCyncDevice(env.api, accessory, device, deviceName, deviceId);
|
|
30
|
+
|
|
31
|
+
// Ensure context is initialized
|
|
32
|
+
const ctx = accessory.context as CyncAccessoryContext;
|
|
33
|
+
ctx.cync = ctx.cync ?? {
|
|
34
|
+
meshId: mesh.id,
|
|
35
|
+
deviceId,
|
|
36
|
+
productId: device.product_id,
|
|
37
|
+
on: false,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Remember mapping for LAN updates
|
|
41
|
+
env.registerAccessoryForDevice(deviceId, accessory);
|
|
42
|
+
env.markDeviceSeen(deviceId);
|
|
43
|
+
env.startPollingDevice(deviceId);
|
|
44
|
+
|
|
45
|
+
service
|
|
46
|
+
.getCharacteristic(env.api.hap.Characteristic.On)
|
|
47
|
+
.onGet(() => {
|
|
48
|
+
const currentOn = !!ctx.cync?.on;
|
|
49
|
+
|
|
50
|
+
if (env.isDeviceProbablyOffline(deviceId)) {
|
|
51
|
+
env.log.debug(
|
|
52
|
+
'Cync: Switch On.get offline-heuristic hit; returning cached=%s for %s (deviceId=%s)',
|
|
53
|
+
String(currentOn),
|
|
54
|
+
deviceName,
|
|
55
|
+
deviceId,
|
|
56
|
+
);
|
|
57
|
+
return currentOn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
env.log.info(
|
|
61
|
+
'Cync: On.get -> %s for %s (deviceId=%s)',
|
|
62
|
+
String(currentOn),
|
|
63
|
+
deviceName,
|
|
64
|
+
deviceId,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return currentOn;
|
|
68
|
+
})
|
|
69
|
+
.onSet(async (value) => {
|
|
70
|
+
const cyncMeta = ctx.cync;
|
|
71
|
+
|
|
72
|
+
if (!cyncMeta?.deviceId) {
|
|
73
|
+
env.log.warn(
|
|
74
|
+
'Cync: Switch On.set called for %s but no cync.deviceId in context',
|
|
75
|
+
deviceName,
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const on = value === true || value === 1;
|
|
81
|
+
|
|
82
|
+
env.log.info(
|
|
83
|
+
'Cync: Switch On.set -> %s for %s (deviceId=%s)',
|
|
84
|
+
String(on),
|
|
85
|
+
deviceName,
|
|
86
|
+
cyncMeta.deviceId,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Optimistic cache
|
|
90
|
+
cyncMeta.on = on;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (!on) {
|
|
94
|
+
await env.tcpClient.setSwitchState(cyncMeta.deviceId, { on: false });
|
|
95
|
+
env.markDeviceSeen(cyncMeta.deviceId);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (cyncMeta.colorActive && cyncMeta.rgb && typeof cyncMeta.brightness === 'number') {
|
|
100
|
+
await env.tcpClient.setColor(cyncMeta.deviceId, cyncMeta.rgb, cyncMeta.brightness);
|
|
101
|
+
} else {
|
|
102
|
+
await env.tcpClient.setSwitchState(cyncMeta.deviceId, { on: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
env.markDeviceSeen(cyncMeta.deviceId);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
env.log.warn(
|
|
108
|
+
'Cync: Switch On.set failed for %s (deviceId=%s): %s',
|
|
109
|
+
deviceName,
|
|
110
|
+
cyncMeta.deviceId,
|
|
111
|
+
(err as Error).message ?? String(err),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
throw new env.api.hap.HapStatusError(
|
|
115
|
+
env.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -30,22 +30,27 @@ export const DEVICE_CATALOG: Record<number, CyncDeviceModel> = {
|
|
|
30
30
|
marketingName: 'Cync reveal HD+',
|
|
31
31
|
// defaultCategory: Categories.LIGHTBULB,
|
|
32
32
|
},
|
|
33
|
+
// Legacy C by GE On/Off Smart Plug — original hardware
|
|
33
34
|
64: {
|
|
34
35
|
deviceType: 64,
|
|
35
|
-
modelName: 'Indoor Smart Plug',
|
|
36
|
-
marketingName: 'On/Off Smart Plug',
|
|
36
|
+
modelName: 'Indoor Smart Plug (CPLGSTDBLW1)',
|
|
37
|
+
marketingName: 'On/Off Smart Plug (CPLGSTDBLW1)',
|
|
38
|
+
notes: 'Legacy C by GE plug. FCC ID PUU-CPLGSTDBLW1. Original hardware revision. Final firmware 1.x.',
|
|
37
39
|
// defaultCategory: Categories.OUTLET,
|
|
38
40
|
},
|
|
41
|
+
// Legacy C by GE On/Off Smart Plug — revised hardware ("T" revision)
|
|
39
42
|
65: {
|
|
40
43
|
deviceType: 65,
|
|
41
|
-
modelName: 'Indoor Smart Plug',
|
|
42
|
-
marketingName: '
|
|
44
|
+
modelName: 'Indoor Smart Plug (CPLGSTDBLW1-T)',
|
|
45
|
+
marketingName: 'On/Off Smart Plug (CPLGSTDBLW1-T)',
|
|
46
|
+
notes: 'Legacy C by GE plug. FCC ID PUU-CPLGSTDBLW1T / HVIN CPLGSTDBLW1T. Revised hardware. Final firmware 2.x.',
|
|
43
47
|
// defaultCategory: Categories.OUTLET,
|
|
44
48
|
},
|
|
45
49
|
172: {
|
|
46
50
|
deviceType: 172,
|
|
47
51
|
modelName: 'Indoor Smart Plug (3in1)',
|
|
48
52
|
marketingName: 'Cync Indoor Smart Plug',
|
|
53
|
+
notes: 'Matter-capable hardware. Replaces legacy C by GE On/Off Smart Plug.',
|
|
49
54
|
// defaultCategory: Categories.OUTLET,
|
|
50
55
|
},
|
|
51
56
|
};
|