homebridge-cync-app 0.0.2

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.
Files changed (50) hide show
  1. package/.editorconfig +10 -0
  2. package/CHANGELOG.md +33 -0
  3. package/LICENSE +176 -0
  4. package/README.md +67 -0
  5. package/config.schema.json +39 -0
  6. package/dist/cync/config-client.d.ts +77 -0
  7. package/dist/cync/config-client.js +222 -0
  8. package/dist/cync/config-client.js.map +1 -0
  9. package/dist/cync/cync-client.d.ts +76 -0
  10. package/dist/cync/cync-client.js +236 -0
  11. package/dist/cync/cync-client.js.map +1 -0
  12. package/dist/cync/tcp-client.d.ts +33 -0
  13. package/dist/cync/tcp-client.js +59 -0
  14. package/dist/cync/tcp-client.js.map +1 -0
  15. package/dist/cync/token-store.d.ts +16 -0
  16. package/dist/cync/token-store.js +39 -0
  17. package/dist/cync/token-store.js.map +1 -0
  18. package/dist/cync/types.d.ts +1 -0
  19. package/dist/cync/types.js +2 -0
  20. package/dist/cync/types.js.map +1 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.js +10 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/platform.d.ts +29 -0
  25. package/dist/platform.js +143 -0
  26. package/dist/platform.js.map +1 -0
  27. package/dist/platformAccessory.d.ts +13 -0
  28. package/dist/platformAccessory.js +17 -0
  29. package/dist/platformAccessory.js.map +1 -0
  30. package/dist/settings.d.ts +2 -0
  31. package/dist/settings.js +3 -0
  32. package/dist/settings.js.map +1 -0
  33. package/docs/cync-api-notes.md +168 -0
  34. package/docs/cync-client-contract.md +172 -0
  35. package/docs/cync-device-model.md +129 -0
  36. package/eslint.config.js +41 -0
  37. package/homebridge-cync-app-v0.0.1.zip +0 -0
  38. package/nodemon.json +12 -0
  39. package/package.json +56 -0
  40. package/src/@types/homebridge-lib.d.ts +14 -0
  41. package/src/cync/config-client.ts +370 -0
  42. package/src/cync/cync-client.ts +408 -0
  43. package/src/cync/tcp-client.ts +88 -0
  44. package/src/cync/token-store.ts +50 -0
  45. package/src/cync/types.ts +0 -0
  46. package/src/index.ts +12 -0
  47. package/src/platform.ts +209 -0
  48. package/src/platformAccessory.ts +18 -0
  49. package/src/settings.ts +3 -0
  50. package/tsconfig.json +24 -0
@@ -0,0 +1,143 @@
1
+ import { PLATFORM_NAME } from './settings.js';
2
+ import { CyncClient } from './cync/cync-client.js';
3
+ import { ConfigClient } from './cync/config-client.js';
4
+ import { TcpClient } from './cync/tcp-client.js';
5
+ const toCyncLogger = (log) => ({
6
+ debug: log.debug.bind(log),
7
+ info: log.info.bind(log),
8
+ warn: log.warn.bind(log),
9
+ error: log.error.bind(log),
10
+ });
11
+ /**
12
+ * CyncAppPlatform
13
+ *
14
+ * Homebridge platform class responsible for:
15
+ * - Initializing the Cync client
16
+ * - Managing cached accessories
17
+ * - Kicking off device discovery from Cync cloud
18
+ */
19
+ export class CyncAppPlatform {
20
+ accessories = [];
21
+ log;
22
+ api;
23
+ config;
24
+ client;
25
+ cloudConfig = null;
26
+ constructor(log, config, api) {
27
+ this.log = log;
28
+ this.config = config;
29
+ this.api = api;
30
+ // Extract login config from platform config
31
+ const cfg = this.config;
32
+ const username = (cfg.username ?? cfg.email);
33
+ const password = cfg.password;
34
+ 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)), {
38
+ email: username ?? '',
39
+ password: password ?? '',
40
+ twoFactor,
41
+ }, this.api.user.storagePath(), toCyncLogger(this.log));
42
+ this.log.info(this.config.name ?? PLATFORM_NAME, 'initialized');
43
+ this.api.on('didFinishLaunching', () => {
44
+ this.log.info(PLATFORM_NAME, 'didFinishLaunching');
45
+ void this.loadCync();
46
+ });
47
+ }
48
+ /**
49
+ * Called when cached accessories are restored from disk.
50
+ */
51
+ configureAccessory(accessory) {
52
+ this.log.info('Restoring cached accessory', accessory.displayName);
53
+ this.accessories.push(accessory);
54
+ }
55
+ async loadCync() {
56
+ try {
57
+ const cfg = this.config;
58
+ const username = (cfg.username ?? cfg.email);
59
+ const password = cfg.password;
60
+ if (!username || !password) {
61
+ this.log.warn('Cync: credentials missing in config.json; skipping cloud login.');
62
+ return;
63
+ }
64
+ // Let CyncClient handle 2FA bootstrap + token persistence.
65
+ const loggedIn = await this.client.ensureLoggedIn();
66
+ if (!loggedIn) {
67
+ // We either just requested a 2FA code or hit a credential error.
68
+ // In the "code requested" case, the log already tells the user
69
+ // to add it to config and restart.
70
+ return;
71
+ }
72
+ const cloudConfig = await this.client.loadConfiguration();
73
+ this.cloudConfig = cloudConfig;
74
+ this.log.info('Cync: cloud configuration loaded; mesh count=%d', cloudConfig?.meshes?.length ?? 0);
75
+ this.discoverDevices(cloudConfig);
76
+ }
77
+ catch (err) {
78
+ this.log.error('Cync: cloud login failed: %s', err.message ?? String(err));
79
+ }
80
+ }
81
+ /**
82
+ * 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.
85
+ */
86
+ discoverDevices(cloudConfig) {
87
+ if (!cloudConfig.meshes?.length) {
88
+ this.log.warn('Cync: no meshes returned from cloud; nothing to discover.');
89
+ return;
90
+ }
91
+ for (const mesh of cloudConfig.meshes) {
92
+ const meshName = mesh.name || mesh.id;
93
+ this.log.info('Cync: processing mesh %s', meshName);
94
+ const devices = mesh.devices ?? [];
95
+ if (!devices.length) {
96
+ this.log.info('Cync: mesh %s has no devices.', meshName);
97
+ continue;
98
+ }
99
+ for (const device of devices) {
100
+ const deviceId = `${device.id ??
101
+ device.device_id ??
102
+ device.mac ??
103
+ device.sn ??
104
+ `${mesh.id}-${device.product_id ?? 'unknown'}`}`;
105
+ const preferredName = device.name ??
106
+ device.displayName ??
107
+ undefined;
108
+ const deviceName = preferredName || `Cync Device ${deviceId}`;
109
+ const uuidSeed = `cync-${mesh.id}-${deviceId}`;
110
+ const uuid = this.api.hap.uuid.generate(uuidSeed);
111
+ const existing = this.accessories.find(acc => acc.UUID === uuid);
112
+ if (existing) {
113
+ this.log.info('Cync: using cached accessory for %s (%s)', deviceName, uuidSeed);
114
+ continue;
115
+ }
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);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { PlatformAccessory } from 'homebridge';
2
+ import type { CyncAppPlatform } from './platform.js';
3
+ /**
4
+ * CyncAccessory
5
+ *
6
+ * Placeholder accessory class for future Cync devices.
7
+ * Currently unused; exists only to provide a typed scaffold.
8
+ */
9
+ export declare class CyncAccessory {
10
+ private readonly platform;
11
+ private readonly accessory;
12
+ constructor(platform: CyncAppPlatform, accessory: PlatformAccessory);
13
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CyncAccessory
3
+ *
4
+ * Placeholder accessory class for future Cync devices.
5
+ * Currently unused; exists only to provide a typed scaffold.
6
+ */
7
+ export class CyncAccessory {
8
+ platform;
9
+ accessory;
10
+ constructor(platform, accessory) {
11
+ this.platform = platform;
12
+ this.accessory = accessory;
13
+ // TODO: implement Cync-specific services and characteristics.
14
+ // This placeholder exists to keep the project compiling during early scaffolding.
15
+ }
16
+ }
17
+ //# sourceMappingURL=platformAccessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platformAccessory.js","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IAEP;IACA;IAFlB,YACkB,QAAyB,EACzB,SAA4B;QAD5B,aAAQ,GAAR,QAAQ,CAAiB;QACzB,cAAS,GAAT,SAAS,CAAmB;QAE7C,8DAA8D;QAC9D,kFAAkF;IACnF,CAAC;CACD"}
@@ -0,0 +1,2 @@
1
+ export declare const PLATFORM_NAME = "CyncAppPlatform";
2
+ export declare const PLUGIN_NAME = "homebridge-cync-app";
@@ -0,0 +1,3 @@
1
+ export const PLATFORM_NAME = 'CyncAppPlatform';
2
+ export const PLUGIN_NAME = 'homebridge-cync-app';
3
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAG,qBAAqB,CAAC"}
@@ -0,0 +1,168 @@
1
+ # **docs/cync-api-notes.md**
2
+
3
+ ## **Cync Cloud API Notes**
4
+
5
+ This document summarizes the Cync cloud authentication flow, device configuration endpoints, and the cloud-derived data structures used by the Cync platform. All details were extracted by analyzing the `cync_lights` Home Assistant integration.
6
+
7
+ ---
8
+
9
+ ## **1. API Base Endpoints**
10
+
11
+ Defined in the upstream integration:
12
+
13
+ ```
14
+ API_AUTH = https://api.gelighting.com/v2/user_auth
15
+ API_REQUEST_CODE = https://api.gelighting.com/v2/two_factor/email/verifycode
16
+ API_2FACTOR_AUTH = https://api.gelighting.com/v2/user_auth/two_factor
17
+ API_DEVICES = https://api.gelighting.com/v2/user/{user}/subscribe/devices
18
+ API_DEVICE_INFO = https://api.gelighting.com/v2/product/{product_id}/device/{device_id}/property
19
+ ```
20
+
21
+ The cloud API provides:
22
+
23
+ * Authentication (username/password, optional 2FA)
24
+ * Device enumeration across all homes
25
+ * Room/group enumeration
26
+ * Ability to retrieve device-level capability information
27
+
28
+ All control operations occur through the TCP protocol, not the cloud API.
29
+
30
+ ---
31
+
32
+ ## **2. Authentication Flow**
33
+
34
+ ### **2.1 Primary Authentication**
35
+
36
+ `POST /v2/user_auth`
37
+
38
+ **Request fields:**
39
+
40
+ * `corp_id` — fixed string `"1007d2ad150c4000"`
41
+ * `email`
42
+ * `password`
43
+
44
+ **Success response includes:**
45
+
46
+ * `access_token`
47
+ * `authorize`
48
+ * `user_id`
49
+
50
+ These values are used to construct a **binary login code** for the TCP session.
51
+
52
+ ### **2.2 Two-Factor Initiation**
53
+
54
+ If authentication returns HTTP 400:
55
+
56
+ `POST /v2/two_factor/email/verifycode`
57
+
58
+ **Request fields:**
59
+
60
+ * `corp_id`
61
+ * `email`
62
+ * `local_lang` (`"en-us"`)
63
+
64
+ If successful, a verification code is emailed to the user.
65
+
66
+ ### **2.3 Two-Factor Completion**
67
+
68
+ `POST /v2/user_auth/two_factor`
69
+
70
+ **Request fields:**
71
+
72
+ * `corp_id`
73
+ * `email`
74
+ * `password`
75
+ * `two_factor` — verification code
76
+ * `resource` — fixed value `"abcdefghijklmnop"`
77
+
78
+ On success, the response includes the same fields as primary authentication and allows creation of the TCP login code.
79
+
80
+ ---
81
+
82
+ ## **3. Cloud Configuration Retrieval**
83
+
84
+ ### **3.1 Retrieve Homes**
85
+
86
+ `GET /v2/user/{user_id}/subscribe/devices`
87
+
88
+ **Headers:**
89
+ `Access-Token: <access_token>`
90
+
91
+ Each home entry includes:
92
+
93
+ * `id`
94
+ * `product_id`
95
+ * `name`
96
+
97
+ ### **3.2 Retrieve Devices Within Home**
98
+
99
+ `GET /v2/product/{product_id}/device/{device_id}/property`
100
+
101
+ **Returned fields include:**
102
+
103
+ * `groupsArray` — rooms
104
+ * `bulbsArray` — devices
105
+
106
+ Devices represent switches, plugs, bulbs, sensors, and controllers.
107
+
108
+ ---
109
+
110
+ ## **4. Configuration Assembly Process**
111
+
112
+ The integration processes cloud data into the following components:
113
+
114
+ ### **4.1 Device Lookup Tables**
115
+
116
+ * `home_devices[home_id]`:
117
+ Maps mesh index → device ID using a deterministic function based on `deviceID` and `home.id`.
118
+
119
+ * `home_controllers[home_id]`:
120
+ Controller devices associated with each home. Homes without controllers are discarded.
121
+
122
+ * `switchID_to_homeID`:
123
+ Reverse mapping from controller identifier → home identifier.
124
+
125
+ ### **4.2 Device Records**
126
+
127
+ Each device is assigned a normalized structure containing:
128
+
129
+ * Name
130
+ * Home affiliation
131
+ * Room affiliation (if known)
132
+ * `mesh_id`
133
+ * `switch_id`
134
+ * Capability flags (on/off, brightness, color temp, rgb, motion, ambient light, multielement, wifi control, plug, fan)
135
+
136
+ ### **4.3 Room Records**
137
+
138
+ Each room record includes:
139
+
140
+ * `room_id`
141
+ * `name`
142
+ * `mesh_id`
143
+ * `room_controller`
144
+ * `home_name`
145
+ * List of switches in the room
146
+ * Subgroups and parent relationships
147
+
148
+ Subgroup relationships are validated after all rooms are constructed.
149
+
150
+ ---
151
+
152
+ ## **5. Configuration Output**
153
+
154
+ The upstream integration returns the following final structure:
155
+
156
+ ```
157
+ {
158
+ rooms,
159
+ devices,
160
+ home_devices,
161
+ home_controllers,
162
+ switchID_to_homeID
163
+ }
164
+ ```
165
+
166
+ This dataset defines the full set of controllable Cync devices and establishes the mesh addressing required for TCP commands.
167
+
168
+
@@ -0,0 +1,172 @@
1
+ # **docs/cync-client-contract.md**
2
+
3
+ ## **Cync Client Contract (TypeScript Implementation Outline)**
4
+
5
+ This document defines a platform-independent contract for a TypeScript Cync client. It serves as the foundation for integrating the Cync system into Homebridge.
6
+
7
+ The contract abstracts both the **cloud API layer** and the **TCP transport layer**.
8
+
9
+ ---
10
+
11
+ ## **1. Module Structure**
12
+
13
+ The client consists of two internal subsystems:
14
+
15
+ 1. **ConfigClient**
16
+
17
+ * Implements cloud authentication
18
+ * Retrieves homes, rooms, and devices
19
+ * Produces a structured configuration model
20
+
21
+ 2. **TcpClient**
22
+
23
+ * Opens the binary TCP session
24
+ * Sends command frames
25
+ * Receives device/room/sensor updates
26
+
27
+ A unifying `CyncClient` class composes these subsystems.
28
+
29
+ ---
30
+
31
+ ## **2. ConfigClient Interface**
32
+
33
+ ```
34
+ interface ConfigClient {
35
+ login(username: string, password: string): Promise<AuthResult>;
36
+ submitTwoFactor(code: string): Promise<AuthResult>;
37
+
38
+ getConfig(): Promise<CyncConfig>;
39
+ }
40
+ ```
41
+
42
+ ### **AuthResult**
43
+
44
+ ```
45
+ interface AuthResult {
46
+ authorized: boolean;
47
+ twoFactorRequired?: boolean;
48
+ userId?: string;
49
+ accessToken?: string;
50
+ authorizeToken?: string;
51
+ loginCode?: Uint8Array; // binary login sequence for TCP
52
+ }
53
+ ```
54
+
55
+ ### **CyncConfig**
56
+
57
+ ```
58
+ interface CyncConfig {
59
+ homes: CyncHome[];
60
+ rooms: { [roomId: string]: CyncRoom };
61
+ devices: { [deviceId: string]: CyncDevice };
62
+ homeDevices: { [homeId: string]: { [meshIndex: number]: string } };
63
+ homeControllers: { [homeId: string]: string[] };
64
+ switchIdToHomeId: { [switchId: string]: string };
65
+ }
66
+ ```
67
+
68
+ This structure mirrors the complete configuration returned by the upstream integration.
69
+
70
+ ---
71
+
72
+ ## **3. TcpClient Interface**
73
+
74
+ ```
75
+ interface TcpClient {
76
+ connect(loginCode: Uint8Array, config: CyncConfig): Promise<void>;
77
+ disconnect(): Promise<void>;
78
+
79
+ onDeviceUpdate(cb: (update: DeviceUpdate) => void): void;
80
+ onRoomUpdate(cb: (update: RoomUpdate) => void): void;
81
+ onMotionUpdate(cb: (update: MotionUpdate) => void): void;
82
+ onAmbientUpdate(cb: (update: AmbientUpdate) => void): void;
83
+
84
+ setSwitchState(deviceId: string, params: SwitchParams): Promise<void>;
85
+ }
86
+ ```
87
+
88
+ ### **Support Types**
89
+
90
+ ```
91
+ interface DeviceUpdate {
92
+ deviceId: string;
93
+ on?: boolean;
94
+ brightness?: number;
95
+ colorTemp?: number;
96
+ rgb?: { r: number; g: number; b: number };
97
+ }
98
+
99
+ interface RoomUpdate {
100
+ roomId: string;
101
+ state: any; // full room state payload
102
+ }
103
+
104
+ interface MotionUpdate {
105
+ deviceId: string;
106
+ motion: boolean;
107
+ }
108
+
109
+ interface AmbientUpdate {
110
+ deviceId: string;
111
+ lux: number;
112
+ }
113
+
114
+ interface SwitchParams {
115
+ on?: boolean;
116
+ brightness?: number;
117
+ colorTemp?: number;
118
+ rgb?: { r: number; g: number; b: number };
119
+ }
120
+ ```
121
+
122
+ ### **Behavior Requirements**
123
+
124
+ * Must open a TLS connection to `cm.gelighting.com:23779` (with fallbacks).
125
+ * Must send `loginCode` immediately after connection.
126
+ * Must parse incoming Cync binary frames.
127
+ * Must maintain keepalive frames.
128
+ * Must track pending commands using sequence numbers.
129
+ * Must resolve promises on acknowledgement or timeout.
130
+
131
+ ---
132
+
133
+ ## **4. Unified CyncClient Interface**
134
+
135
+ ```
136
+ class CyncClient {
137
+ constructor(configClient: ConfigClient, tcpClient: TcpClient);
138
+
139
+ authenticate(username: string, password: string): Promise<AuthResult>;
140
+ submitTwoFactor(code: string): Promise<AuthResult>;
141
+
142
+ loadConfiguration(): Promise<CyncConfig>;
143
+
144
+ startTransport(config: CyncConfig, loginCode: Uint8Array): Promise<void>;
145
+ stopTransport(): Promise<void>;
146
+
147
+ setSwitchState(deviceId: string, params: SwitchParams): Promise<void>;
148
+
149
+ onDeviceUpdate(cb): void;
150
+ onRoomUpdate(cb): void;
151
+ onMotionUpdate(cb): void;
152
+ onAmbientUpdate(cb): void;
153
+ }
154
+ ```
155
+
156
+ The unified interface ensures that Homebridge only interacts with a single cohesive API, independent of cloud or TCP implementation details.
157
+
158
+ ---
159
+
160
+ ## **5. Scope of the Contract**
161
+
162
+ This contract provides the baseline functionality for:
163
+
164
+ * Plug control
165
+ * Light control (brightness, color temperature, RGB)
166
+ * Scene and group operations (room-level control)
167
+ * Sensor updates (motion, ambient light)
168
+ * Live state synchronization
169
+
170
+ This set of capabilities is sufficient for initial Homebridge platform support and can be extended incrementally.
171
+
172
+ ---
@@ -0,0 +1,129 @@
1
+ # **docs/cync-device-model.md**
2
+
3
+ ## **Cync Device and Room Model**
4
+
5
+ This document defines the normalized structures used to represent Cync homes, rooms, devices, controllers, and capabilities. These definitions are derived from the behavior of the upstream Home Assistant integration.
6
+
7
+ ---
8
+
9
+ ## **1. Data Model Overview**
10
+
11
+ The Cync platform divides user resources into:
12
+
13
+ * **Homes**
14
+ * **Rooms (Groups)**
15
+ * **Devices (Switches, Plugs, Bulbs, Fan Switches, Motion/Ambient Sensors)**
16
+ * **Controllers (Wi-Fi switches controlling BLE mesh groups)**
17
+
18
+ The device model is primarily determined by cloud configuration, while device state is handled through the TCP protocol.
19
+
20
+ ---
21
+
22
+ ## **2. Home Structure**
23
+
24
+ ```
25
+ CyncHome {
26
+ id: string
27
+ name: string
28
+ controllers: number[] // switch_id values
29
+ homeDevices: { [meshIndex: number]: deviceId }
30
+ }
31
+ ```
32
+
33
+ ### **Properties**
34
+
35
+ * A home exists only if it contains at least one valid controller.
36
+ * Controller devices act as gateways for mesh devices over the TCP protocol.
37
+
38
+ ---
39
+
40
+ ## **3. Room Structure**
41
+
42
+ ```
43
+ CyncRoom {
44
+ roomId: string // "{homeId}-{groupId}"
45
+ name: string
46
+ homeId: string
47
+ meshId: number // group ID
48
+ roomController: string // associated controller switch_id
49
+ switches: string[] // device IDs belonging to this room
50
+ isSubgroup: boolean
51
+ subgroups: string[] // child rooms
52
+ parentRoom?: string // resolved later
53
+ }
54
+ ```
55
+
56
+ ### **Notes**
57
+
58
+ * Subgroups are validated as part of configuration assembly.
59
+ * Some room types represent multi-element groupings.
60
+
61
+ ---
62
+
63
+ ## **4. Device Structure**
64
+
65
+ ```
66
+ CyncDevice {
67
+ id: string
68
+ name: string
69
+ homeId: string
70
+ roomId?: string
71
+ roomName?: string
72
+ meshId: number
73
+ switchId: string // controller association or "0"
74
+ capabilities: CyncCapabilities
75
+ }
76
+ ```
77
+
78
+ ### **Behavior**
79
+
80
+ * Devices may represent plugs, bulbs, switches, fan switches, or sensors.
81
+ * Capabilities determine which HomeKit services will be exposed.
82
+
83
+ ---
84
+
85
+ ## **5. Capability Flags**
86
+
87
+ ```
88
+ CyncCapabilities {
89
+ onOff: boolean
90
+ brightness: boolean
91
+ colorTemp: boolean
92
+ rgb: boolean
93
+
94
+ motion: boolean
95
+ ambientLight: boolean
96
+
97
+ wifiControl: boolean // device is a controller
98
+ plug: boolean // device is a smart plug
99
+ fan: boolean // device is a fan switch
100
+
101
+ multiElement?: number // number of segments in multi-gang switches
102
+ }
103
+ ```
104
+
105
+ These flags originate from a fixed mapping inside the upstream integration and are chosen based on the device’s reported type.
106
+
107
+ ---
108
+
109
+ ## **6. Controller Relationships**
110
+
111
+ Controllers bridge Wi-Fi → Cync mesh.
112
+
113
+ * Devices with `wifiControl == true` identify themselves as controllers.
114
+ * `switchID_to_homeID` maps each controller to an owning home.
115
+ * Devices without `switchId` or with `"0"` are assumed to be mesh-only nodes and must route commands through their associated controller.
116
+
117
+ ---
118
+
119
+ ## **7. Summary**
120
+
121
+ The model above defines the minimal structured representation required to:
122
+
123
+ * Construct Homebridge accessories.
124
+ * Route state updates.
125
+ * Build correct command frames (requires `switchId`, `meshId`).
126
+ * Group devices into rooms.
127
+ * Build user-facing metadata for configuration.
128
+
129
+ ---