homebridge-cync-app 0.0.2 → 0.1.1

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/dist/platform.js CHANGED
@@ -22,7 +22,70 @@ export class CyncAppPlatform {
22
22
  api;
23
23
  config;
24
24
  client;
25
+ tcpClient;
25
26
  cloudConfig = null;
27
+ deviceIdToAccessory = new Map();
28
+ handleLanUpdate(update) {
29
+ // We only care about parsed 0x83 frames that look like:
30
+ // { controllerId: number, on: boolean, level: number, deviceId?: string }
31
+ const payload = update;
32
+ if (!payload || typeof payload.deviceId !== 'string' || typeof payload.on !== 'boolean') {
33
+ return;
34
+ }
35
+ const accessory = this.deviceIdToAccessory.get(payload.deviceId);
36
+ if (!accessory) {
37
+ this.log.debug('Cync: LAN update for unknown deviceId=%s; no accessory mapping', payload.deviceId);
38
+ return;
39
+ }
40
+ const service = accessory.getService(this.api.hap.Service.Switch);
41
+ if (!service) {
42
+ this.log.debug('Cync: accessory %s has no Switch service for deviceId=%s', accessory.displayName, payload.deviceId);
43
+ return;
44
+ }
45
+ // Update cached context state
46
+ const ctx = accessory.context;
47
+ ctx.cync = ctx.cync ?? {
48
+ meshId: '',
49
+ deviceId: payload.deviceId,
50
+ };
51
+ ctx.cync.on = payload.on;
52
+ this.log.info('Cync: LAN update -> %s is now %s (deviceId=%s)', accessory.displayName, payload.on ? 'ON' : 'OFF', payload.deviceId);
53
+ // Push the new state into HomeKit
54
+ service.updateCharacteristic(this.api.hap.Characteristic.On, payload.on);
55
+ }
56
+ configureCyncSwitchAccessory(mesh, device, accessory, deviceName, deviceId) {
57
+ const service = accessory.getService(this.api.hap.Service.Switch) ||
58
+ accessory.addService(this.api.hap.Service.Switch, deviceName);
59
+ // Ensure context is initialized
60
+ const ctx = accessory.context;
61
+ ctx.cync = ctx.cync ?? {
62
+ meshId: mesh.id,
63
+ deviceId,
64
+ productId: device.product_id,
65
+ on: false,
66
+ };
67
+ // Remember mapping for LAN updates
68
+ this.deviceIdToAccessory.set(deviceId, accessory);
69
+ service
70
+ .getCharacteristic(this.api.hap.Characteristic.On)
71
+ .onGet(() => {
72
+ const currentOn = !!ctx.cync?.on;
73
+ this.log.info('Cync: On.get -> %s for %s (deviceId=%s)', String(currentOn), deviceName, deviceId);
74
+ return currentOn;
75
+ })
76
+ .onSet(async (value) => {
77
+ const cyncMeta = ctx.cync;
78
+ if (!cyncMeta?.deviceId) {
79
+ this.log.warn('Cync: On.set called for %s but no cync.deviceId in context', deviceName);
80
+ return;
81
+ }
82
+ const on = value === true || value === 1;
83
+ this.log.info('Cync: On.set -> %s for %s (deviceId=%s)', String(on), deviceName, cyncMeta.deviceId);
84
+ // Optimistic local cache; LAN update will confirm
85
+ cyncMeta.on = on;
86
+ await this.tcpClient.setSwitchState(cyncMeta.deviceId, { on });
87
+ });
88
+ }
26
89
  constructor(log, config, api) {
27
90
  this.log = log;
28
91
  this.config = config;
@@ -32,13 +95,18 @@ export class CyncAppPlatform {
32
95
  const username = (cfg.username ?? cfg.email);
33
96
  const password = cfg.password;
34
97
  const twoFactor = cfg.twoFactor;
35
- // Initialize the Cync client with platform logger so all messages
36
- // appear in the Homebridge log.
37
- this.client = new CyncClient(new ConfigClient(toCyncLogger(this.log)), new TcpClient(toCyncLogger(this.log)), {
98
+ const cyncLogger = toCyncLogger(this.log);
99
+ const tcpClient = new TcpClient(cyncLogger);
100
+ this.client = new CyncClient(new ConfigClient(cyncLogger), tcpClient, {
38
101
  email: username ?? '',
39
102
  password: password ?? '',
40
103
  twoFactor,
41
- }, this.api.user.storagePath(), toCyncLogger(this.log));
104
+ }, this.api.user.storagePath(), cyncLogger);
105
+ this.tcpClient = tcpClient;
106
+ // Bridge LAN updates into Homebridge
107
+ this.client.onLanDeviceUpdate((update) => {
108
+ this.handleLanUpdate(update);
109
+ });
42
110
  this.log.info(this.config.name ?? PLATFORM_NAME, 'initialized');
43
111
  this.api.on('didFinishLaunching', () => {
44
112
  this.log.info(PLATFORM_NAME, 'didFinishLaunching');
@@ -71,7 +139,24 @@ export class CyncAppPlatform {
71
139
  }
72
140
  const cloudConfig = await this.client.loadConfiguration();
73
141
  this.cloudConfig = cloudConfig;
74
- this.log.info('Cync: cloud configuration loaded; mesh count=%d', cloudConfig?.meshes?.length ?? 0);
142
+ this.log.info('Cync: cloud configuration loaded; mesh count=%d', cloudConfig.meshes.length);
143
+ // Ask the CyncClient for the LAN login code derived from stored session.
144
+ // If it returns an empty blob, LAN is disabled but cloud still works.
145
+ let loginCode = new Uint8Array();
146
+ try {
147
+ loginCode = this.client.getLanLoginCode();
148
+ }
149
+ catch (err) {
150
+ this.log.warn('Cync: getLanLoginCode() failed: %s', err.message ?? String(err));
151
+ }
152
+ if (loginCode.length > 0) {
153
+ this.log.info('Cync: LAN login code available (%d bytes); starting TCP transport…', loginCode.length);
154
+ // ### 🧩 LAN Transport Bootstrap: wire frame listeners via CyncClient
155
+ await this.client.startTransport(cloudConfig, loginCode);
156
+ }
157
+ else {
158
+ this.log.info('Cync: LAN login code unavailable; TCP control disabled (cloud-only).');
159
+ }
75
160
  this.discoverDevices(cloudConfig);
76
161
  }
77
162
  catch (err) {
@@ -80,8 +165,7 @@ export class CyncAppPlatform {
80
165
  }
81
166
  /**
82
167
  * Discover devices from the Cync cloud config and register them as
83
- * Homebridge accessories. For now, each device is exposed as a simple
84
- * dummy Switch that logs state changes.
168
+ * Homebridge accessories.
85
169
  */
86
170
  discoverDevices(cloudConfig) {
87
171
  if (!cloudConfig.meshes?.length) {
@@ -97,45 +181,28 @@ export class CyncAppPlatform {
97
181
  continue;
98
182
  }
99
183
  for (const device of devices) {
100
- const deviceId = `${device.id ??
101
- device.device_id ??
184
+ const deviceId = device.device_id ??
185
+ device.id ??
102
186
  device.mac ??
103
187
  device.sn ??
104
- `${mesh.id}-${device.product_id ?? 'unknown'}`}`;
188
+ `${mesh.id}-${device.product_id ?? 'unknown'}`;
105
189
  const preferredName = device.name ??
106
190
  device.displayName ??
107
191
  undefined;
108
192
  const deviceName = preferredName || `Cync Device ${deviceId}`;
109
193
  const uuidSeed = `cync-${mesh.id}-${deviceId}`;
110
194
  const uuid = this.api.hap.uuid.generate(uuidSeed);
111
- const existing = this.accessories.find(acc => acc.UUID === uuid);
112
- if (existing) {
195
+ let accessory = this.accessories.find(acc => acc.UUID === uuid);
196
+ if (accessory) {
113
197
  this.log.info('Cync: using cached accessory for %s (%s)', deviceName, uuidSeed);
114
- continue;
115
198
  }
116
- this.log.info('Cync: registering new accessory for %s (%s)', deviceName, uuidSeed);
117
- const accessory = new this.api.platformAccessory(deviceName, uuid);
118
- // Simple Switch service for now
119
- const service = accessory.getService(this.api.hap.Service.Switch) ||
120
- accessory.addService(this.api.hap.Service.Switch, deviceName);
121
- service
122
- .getCharacteristic(this.api.hap.Characteristic.On)
123
- .onGet(() => {
124
- this.log.info('Cync: On.get -> false for %s', deviceName);
125
- return false;
126
- })
127
- .onSet((value) => {
128
- this.log.info('Cync: On.set -> %s for %s', String(value), deviceName);
129
- });
130
- // Context for later TCP control
131
- const ctx = accessory.context;
132
- ctx.cync = {
133
- meshId: mesh.id,
134
- deviceId,
135
- productId: device.product_id,
136
- };
137
- this.api.registerPlatformAccessories('homebridge-cync-app', 'CyncAppPlatform', [accessory]);
138
- this.accessories.push(accessory);
199
+ else {
200
+ this.log.info('Cync: registering new accessory for %s (%s)', deviceName, uuidSeed);
201
+ accessory = new this.api.platformAccessory(deviceName, uuid);
202
+ this.api.registerPlatformAccessories('homebridge-cync-app', 'CyncAppPlatform', [accessory]);
203
+ this.accessories.push(accessory);
204
+ }
205
+ this.configureCyncSwitchAccessory(mesh, device, accessory, deviceName, deviceId);
139
206
  }
140
207
  }
141
208
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGjD,MAAM,YAAY,GAAG,CAAC,GAAW,EAAc,EAAE,CAAC,CAAC;IAClD,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IAC1B,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;CAC1B,CAAC,CAAC;AAWH;;;;;;;GAOG;AACH,MAAM,OAAO,eAAe;IACX,WAAW,GAAwB,EAAE,CAAC;IAErC,GAAG,CAAS;IACZ,GAAG,CAAM;IACT,MAAM,CAAiB;IACvB,MAAM,CAAa;IAE5B,WAAW,GAA2B,IAAI,CAAC;IAEnD,YAAY,GAAW,EAAE,MAAsB,EAAE,GAAQ;QACxD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QAEf,4CAA4C;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAiC,CAAC;QACnD,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAuB,CAAC;QACnE,MAAM,QAAQ,GAAG,GAAG,CAAC,QAA8B,CAAC;QACpD,MAAM,SAAS,GAAG,GAAG,CAAC,SAA+B,CAAC;QAEtD,kEAAkE;QAClE,gCAAgC;QAChC,IAAI,CAAC,MAAM,GAAG,IAAI,UAAU,CAC3B,IAAI,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACxC,IAAI,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACrC;YACC,KAAK,EAAE,QAAQ,IAAI,EAAE;YACrB,QAAQ,EAAE,QAAQ,IAAI,EAAE;YACxB,SAAS;SACT,EACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAC3B,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CACtB,CAAC;QAEF,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,aAAa,EAAE,aAAa,CAAC,CAAC;QAEhE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;YACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;YACnD,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,kBAAkB,CAAC,SAA4B;QAC9C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,4BAA4B,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;QACnE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IAEO,KAAK,CAAC,QAAQ;QACrB,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,IAAI,CAAC,MAAiC,CAAC;YACnD,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAuB,CAAC;YACnE,MAAM,QAAQ,GAAG,GAAG,CAAC,QAA8B,CAAC;YAEpD,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC5B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;gBACjF,OAAO;YACR,CAAC;YAED,2DAA2D;YAC3D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,iEAAiE;gBACjE,+DAA+D;gBAC/D,mCAAmC;gBACnC,OAAO;YACR,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC1D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;YAE/B,IAAI,CAAC,GAAG,CAAC,IAAI,CACZ,iDAAiD,EACjD,WAAW,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC,CAChC,CAAC;YAEF,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,GAAG,CAAC,KAAK,CACb,8BAA8B,EAC7B,GAAa,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CACrC,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,eAAe,CAAC,WAA4B;QACnD,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;YAC3E,OAAO;QACR,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,0BAA0B,EAAE,QAAQ,CAAC,CAAC;YAEpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBACrB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,+BAA+B,EAAE,QAAQ,CAAC,CAAC;gBACzD,SAAS;YACV,CAAC;YAED,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,EAAE;oBAC5B,MAAM,CAAC,SAAS;oBAChB,MAAM,CAAC,GAAG;oBACV,MAAM,CAAC,EAAE;oBACT,GAAG,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,UAAU,IAAI,SAAS,EAAE,EAAE,CAAC;gBAElD,MAAM,aAAa,GACjB,MAAM,CAAC,IAA2B;oBAClC,MAAM,CAAC,WAAkC;oBAC1C,SAAS,CAAC;gBAEX,MAAM,UAAU,GAAG,aAAa,IAAI,eAAe,QAAQ,EAAE,CAAC;gBAC9D,MAAM,QAAQ,GAAG,QAAQ,IAAI,CAAC,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAElD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;gBACjE,IAAI,QAAQ,EAAE,CAAC;oBACd,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,0CAA0C,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;oBAChF,SAAS;gBACV,CAAC;gBAED,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,6CAA6C,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;gBAEnF,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBAEnE,gCAAgC;gBAChC,MAAM,OAAO,GACZ,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;oBACjD,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;gBAE/D,OAAO;qBACL,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;qBACjD,KAAK,CAAC,GAAG,EAAE;oBACX,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,8BAA8B,EAAE,UAAU,CAAC,CAAC;oBAC1D,OAAO,KAAK,CAAC;gBACd,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBAChB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,2BAA2B,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC;gBACvE,CAAC,CAAC,CAAC;gBAEJ,gCAAgC;gBAChC,MAAM,GAAG,GAAG,SAAS,CAAC,OAA+B,CAAC;gBACtD,GAAG,CAAC,IAAI,GAAG;oBACV,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,QAAQ;oBACR,SAAS,EAAE,MAAM,CAAC,UAAU;iBAC5B,CAAC;gBAEF,IAAI,CAAC,GAAG,CAAC,2BAA2B,CACnC,qBAAqB,EACrB,iBAAiB,EACjB,CAAC,SAAS,CAAC,CACX,CAAC;gBAEF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC;CACD"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -77,6 +77,19 @@ If successful, a verification code is emailed to the user.
77
77
 
78
78
  On success, the response includes the same fields as primary authentication and allows creation of the TCP login code.
79
79
 
80
+ ### **2.4 Token Persistence (Homebridge Plugin Behavior)**
81
+
82
+ After successful 2FA login, the Homebridge plugin stores:
83
+
84
+ - `access_token`
85
+ - `user_id`
86
+ - optional `refreshToken`
87
+ - optional `expiresAt`
88
+
89
+ Tokens are restored on startup via a local `cync-token.json` file.
90
+ The Cync API often omits `expiresAt`, so the plugin must treat tokens as
91
+ semi-permanent until an API error indicates otherwise.
92
+
80
93
  ---
81
94
 
82
95
  ## **3. Cloud Configuration Retrieval**
@@ -94,19 +107,93 @@ Each home entry includes:
94
107
  * `product_id`
95
108
  * `name`
96
109
 
97
- ### **3.2 Retrieve Devices Within Home**
110
+ #### Actual Response Structure (Observed)
98
111
 
99
- `GET /v2/product/{product_id}/device/{device_id}/property`
112
+ Live queries show that `/subscribe/devices` returns **mesh-level objects**, *not*
113
+ device lists. These objects include metadata fields but no per-device entries.
100
114
 
101
- **Returned fields include:**
115
+ Discovered keys:
102
116
 
103
- * `groupsArray` — rooms
104
- * `bulbsArray` — devices
117
+ - id
118
+ - name
119
+ - mac
120
+ - product_id
121
+ - access_key
122
+ - authorize_code
123
+ - role
124
+ - source
125
+ - is_active
126
+ - is_online
127
+ - subscribe_date
128
+ - active_date
129
+ - last_login
130
+ - mcu_version
131
+ - firmware_version
132
+ - groups
105
133
 
106
- Devices represent switches, plugs, bulbs, sensors, and controllers.
134
+ ⚠️ **Important:**
135
+ These meshes do *not* contain devices. Device lists are obtained from
136
+ `/product/<product_id>/device/<mesh_id>/property` (see Section 3.2).
107
137
 
108
- ---
138
+ ### **3.2 Retrieve Devices Within a Mesh**
139
+
140
+ `GET /v2/product/<product_id>/device/<mesh_id>/property`
141
+
142
+ This endpoint returns the **actual list of devices** in a mesh.
143
+ The top-level object includes fields such as:
144
+
145
+ - `groupsArray`
146
+ - `sceneArray`
147
+ - `schedules`
148
+ - `system`
149
+ - `deviceIdRecord`
150
+ - `ftsModel`
151
+ - `bulbsArray` (primary device list)
152
+
153
+ #### **Device Structure (from bulbsArray)**
154
+
155
+ Each entry in `bulbsArray` represents a real Cync device such as a plug, switch,
156
+ bulb, or sensor.
157
+
158
+ Observed device keys:
159
+
160
+ - `displayName`
161
+ - `deviceID`
162
+ - `wifiMac`
163
+ - `deviceType`
164
+ - `firmwareVersion`
165
+ - `fadeOn`, `fadeOff`
166
+ - `defaultBrightness`
167
+ - `ambientLightEnable`
168
+ - `ambientLightSensitivity`
169
+ - `occupancyEnable`
170
+ - `occupancyTimeoutPeriod`
171
+ - `followTheSun`
172
+ - `simpleModeEnabled`
173
+ - `wifiSsid`
174
+ - `wifiDisconnectIndicatorEnable`
175
+
176
+ These fields provide enough metadata for:
177
+
178
+ - Naming the accessory
179
+ - Generating stable UUIDs in Homebridge
180
+ - Determining capabilities (switch vs dimmer vs sensor)
181
+
182
+ ### **3.3 Discovery Model Summary**
109
183
 
184
+ Cync cloud discovery requires combining two endpoints:
185
+
186
+ 1. `/subscribe/devices`
187
+ Provides *mesh-level* information (homes/networks) but no per-device data.
188
+
189
+ 2. `/product/<product_id>/device/<mesh_id>/property`
190
+ Provides the **actual device list** via `bulbsArray`.
191
+
192
+ Because of this structure:
193
+
194
+ - A mesh may appear empty in `/subscribe/devices` but still contain devices.
195
+ - Device names and IDs must be extracted solely from `bulbsArray`.
196
+ ---
110
197
  ## **4. Configuration Assembly Process**
111
198
 
112
199
  The integration processes cloud data into the following components:
@@ -122,6 +209,22 @@ The integration processes cloud data into the following components:
122
209
  * `switchID_to_homeID`:
123
210
  Reverse mapping from controller identifier → home identifier.
124
211
 
212
+ #### **UUID Strategy (Homebridge Implementation)**
213
+
214
+ To ensure stable and deterministic accessory identities, device UUIDs are
215
+ generated using:
216
+ `cync-${mesh.id}-${deviceID}`
217
+
218
+ Where:
219
+
220
+ - `deviceID` is taken from `deviceID`
221
+ - If missing, fallback is `wifiMac` (colons removed)
222
+ - Final fallback: `${mesh.id}-${product_id}`
223
+
224
+ This ensures:
225
+ - Accessories do not duplicate on restart
226
+ - Renaming devices in the Cync app does not break HomeKit bindings
227
+
125
228
  ### **4.2 Device Records**
126
229
 
127
230
  Each device is assigned a normalized structure containing:
@@ -165,4 +268,15 @@ The upstream integration returns the following final structure:
165
268
 
166
269
  This dataset defines the full set of controllable Cync devices and establishes the mesh addressing required for TCP commands.
167
270
 
271
+ ### **TCP LAN Control (Not yet implemented)**
272
+
273
+ Cync cloud discovery provides:
274
+
275
+ - product_id
276
+ - access_key
277
+ - meshId
278
+
279
+ These values will be used to establish a LAN session using:
280
+
281
+ `/tcp-client.connect(loginCode, config)`
168
282
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "homebridge-cync-app",
3
3
  "displayName": "Homebridge Cync App",
4
4
  "type": "module",
5
- "version": "0.0.2",
5
+ "version": "0.1.1",
6
6
  "private": false,
7
7
  "description": "Homebridge plugin that integrates your GE Cync account (via the Cync app/API) and exposes all supported devices: plugs, lights, switches, etc",
8
8
  "author": "Dustin Newell",
@@ -19,12 +19,17 @@
19
19
  "homebridge-plugin",
20
20
  "homebridge",
21
21
  "cync",
22
+ "ge",
22
23
  "ge cync",
24
+ "ge-lighting",
23
25
  "smart plug",
24
26
  "smart lights",
25
27
  "C by GE"
26
28
  ],
27
29
  "main": "dist/index.js",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
28
33
  "homebridge": {
29
34
  "pluginType": "platform",
30
35
  "platform": "CyncAppPlatform"
@@ -32,6 +32,7 @@ type CyncErrorBody = {
32
32
  export interface CyncLoginSession {
33
33
  accessToken: string;
34
34
  userId: string;
35
+ authorize?: string;
35
36
  raw: unknown;
36
37
  }
37
38
 
@@ -42,6 +43,10 @@ export interface CyncDevice {
42
43
  device_id?: string;
43
44
  mac?: string;
44
45
  sn?: string;
46
+ switch_id?: string;
47
+ switch_controller?: number | string;
48
+ mesh_id?: number | string;
49
+
45
50
  [key: string]: unknown;
46
51
  }
47
52
 
@@ -84,6 +89,7 @@ export class ConfigClient {
84
89
  // These are populated after a successful 2FA login.
85
90
  private accessToken: string | null = null;
86
91
  private userId: string | null = null;
92
+ private authorize: string | null = null;
87
93
 
88
94
  constructor(logger?: CyncLogger) {
89
95
  this.log = logger ?? defaultLogger;
@@ -182,6 +188,7 @@ export class ConfigClient {
182
188
  // Accept both snake_case and camelCase, and both string/number user_id.
183
189
  const accessTokenRaw = obj.access_token ?? obj.accessToken;
184
190
  const userIdRaw = obj.user_id ?? obj.userId;
191
+ const authorizeRaw = obj.authorize;
185
192
 
186
193
  const accessToken =
187
194
  typeof accessTokenRaw === 'string' && accessTokenRaw.length > 0
@@ -193,6 +200,11 @@ export class ConfigClient {
193
200
  ? String(userIdRaw)
194
201
  : undefined;
195
202
 
203
+ const authorize =
204
+ typeof authorizeRaw === 'string' && authorizeRaw.length > 0
205
+ ? authorizeRaw
206
+ : undefined;
207
+
196
208
  if (!accessToken || !userId) {
197
209
  this.log.error('Cync login missing access_token or user_id: %o', json);
198
210
  throw new Error('Cync login response missing access_token or user_id');
@@ -200,12 +212,14 @@ export class ConfigClient {
200
212
 
201
213
  this.accessToken = accessToken;
202
214
  this.userId = userId;
215
+ this.authorize = authorize ?? null;
203
216
 
204
217
  this.log.info('Cync login successful; userId=%s', userId);
205
218
 
206
219
  return {
207
220
  accessToken,
208
221
  userId,
222
+ authorize,
209
223
  raw: json,
210
224
  };
211
225
  }
@@ -358,6 +372,47 @@ export class ConfigClient {
358
372
  throw new Error('Cync session not initialised. Call loginWithTwoFactor() first.');
359
373
  }
360
374
  }
375
+ // ### 🧩 LAN Login Blob Builder: Generates the auth_code payload used by Cync LAN TCP
376
+ public static buildLanLoginCode(userId: string, authorize: string): Uint8Array {
377
+ const authBytes = Buffer.from(authorize, 'ascii');
378
+ const lengthByte = 10 + authBytes.length;
379
+
380
+ if (lengthByte > 0xff) {
381
+ throw new Error('Cync LAN authorize token too long to encode.');
382
+ }
383
+
384
+ const userIdNum = Number.parseInt(userId, 10);
385
+ if (!Number.isFinite(userIdNum) || userIdNum < 0) {
386
+ throw new Error(`Invalid Cync userId for LAN auth: ${userId}`);
387
+ }
388
+
389
+ const header = Buffer.from('13000000', 'hex');
390
+
391
+ const lenBuf = Buffer.alloc(1);
392
+ lenBuf.writeUInt8(lengthByte & 0xff, 0);
393
+
394
+ const cmdBuf = Buffer.from('03', 'hex');
395
+
396
+ const userIdBuf = Buffer.alloc(4);
397
+ userIdBuf.writeUInt32BE(userIdNum >>> 0, 0);
398
+
399
+ const authLenBuf = Buffer.alloc(2);
400
+ authLenBuf.writeUInt16BE(authBytes.length, 0);
401
+
402
+ const tail = Buffer.from('0000b4', 'hex');
403
+
404
+ const loginBuf = Buffer.concat([
405
+ header,
406
+ lenBuf,
407
+ cmdBuf,
408
+ userIdBuf,
409
+ authLenBuf,
410
+ authBytes,
411
+ tail,
412
+ ]);
413
+
414
+ return new Uint8Array(loginBuf);
415
+ }
361
416
 
362
417
  private static randomLoginResource(): string {
363
418
  const chars = 'abcdefghijklmnopqrstuvwxyz';