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.
- package/.editorconfig +10 -0
- package/CHANGELOG.md +33 -0
- package/LICENSE +176 -0
- package/README.md +67 -0
- package/config.schema.json +39 -0
- package/dist/cync/config-client.d.ts +77 -0
- package/dist/cync/config-client.js +222 -0
- package/dist/cync/config-client.js.map +1 -0
- package/dist/cync/cync-client.d.ts +76 -0
- package/dist/cync/cync-client.js +236 -0
- package/dist/cync/cync-client.js.map +1 -0
- package/dist/cync/tcp-client.d.ts +33 -0
- package/dist/cync/tcp-client.js +59 -0
- package/dist/cync/tcp-client.js.map +1 -0
- package/dist/cync/token-store.d.ts +16 -0
- package/dist/cync/token-store.js +39 -0
- package/dist/cync/token-store.js.map +1 -0
- package/dist/cync/types.d.ts +1 -0
- package/dist/cync/types.js +2 -0
- package/dist/cync/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +29 -0
- package/dist/platform.js +143 -0
- package/dist/platform.js.map +1 -0
- package/dist/platformAccessory.d.ts +13 -0
- package/dist/platformAccessory.js +17 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +3 -0
- package/dist/settings.js.map +1 -0
- package/docs/cync-api-notes.md +168 -0
- package/docs/cync-client-contract.md +172 -0
- package/docs/cync-device-model.md +129 -0
- package/eslint.config.js +41 -0
- package/homebridge-cync-app-v0.0.1.zip +0 -0
- package/nodemon.json +12 -0
- package/package.json +56 -0
- package/src/@types/homebridge-lib.d.ts +14 -0
- package/src/cync/config-client.ts +370 -0
- package/src/cync/cync-client.ts +408 -0
- package/src/cync/tcp-client.ts +88 -0
- package/src/cync/token-store.ts +50 -0
- package/src/cync/types.ts +0 -0
- package/src/index.ts +12 -0
- package/src/platform.ts +209 -0
- package/src/platformAccessory.ts +18 -0
- package/src/settings.ts +3 -0
- package/tsconfig.json +24 -0
package/dist/platform.js
ADDED
|
@@ -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":"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"}
|
|
@@ -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"}
|
package/dist/settings.js
ADDED
|
@@ -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
|
+
---
|