@ecopoesis/homebridge-dmx 0.1.0
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/CLAUDE.md +176 -0
- package/README.md +146 -0
- package/config.schema.json +143 -0
- package/dist/cli/stick.d.ts +3 -0
- package/dist/cli/stick.d.ts.map +1 -0
- package/dist/cli/stick.js +176 -0
- package/dist/cli/stick.js.map +1 -0
- package/dist/color/cct.d.ts +3 -0
- package/dist/color/cct.d.ts.map +1 -0
- package/dist/color/cct.js +33 -0
- package/dist/color/cct.js.map +1 -0
- package/dist/color/dimmer.d.ts +3 -0
- package/dist/color/dimmer.d.ts.map +1 -0
- package/dist/color/dimmer.js +16 -0
- package/dist/color/dimmer.js.map +1 -0
- package/dist/color/hsvcct.d.ts +3 -0
- package/dist/color/hsvcct.d.ts.map +1 -0
- package/dist/color/hsvcct.js +52 -0
- package/dist/color/hsvcct.js.map +1 -0
- package/dist/color/index.d.ts +7 -0
- package/dist/color/index.d.ts.map +1 -0
- package/dist/color/index.js +28 -0
- package/dist/color/index.js.map +1 -0
- package/dist/color/parsers.d.ts +20 -0
- package/dist/color/parsers.d.ts.map +1 -0
- package/dist/color/parsers.js +232 -0
- package/dist/color/parsers.js.map +1 -0
- package/dist/color/rgb.d.ts +3 -0
- package/dist/color/rgb.d.ts.map +1 -0
- package/dist/color/rgb.js +32 -0
- package/dist/color/rgb.js.map +1 -0
- package/dist/color/rgbaw.d.ts +3 -0
- package/dist/color/rgbaw.d.ts.map +1 -0
- package/dist/color/rgbaw.js +48 -0
- package/dist/color/rgbaw.js.map +1 -0
- package/dist/color/rgbw.d.ts +3 -0
- package/dist/color/rgbw.d.ts.map +1 -0
- package/dist/color/rgbw.js +37 -0
- package/dist/color/rgbw.js.map +1 -0
- package/dist/color/rgbww.d.ts +3 -0
- package/dist/color/rgbww.d.ts.map +1 -0
- package/dist/color/rgbww.js +54 -0
- package/dist/color/rgbww.js.map +1 -0
- package/dist/color/types.d.ts +64 -0
- package/dist/color/types.d.ts.map +1 -0
- package/dist/color/types.js +91 -0
- package/dist/color/types.js.map +1 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +191 -0
- package/dist/config.js.map +1 -0
- package/dist/controller.d.ts +32 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +128 -0
- package/dist/controller.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +17 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +96 -0
- package/dist/platform.js.map +1 -0
- package/dist/platformAccessory.d.ts +18 -0
- package/dist/platformAccessory.d.ts.map +1 -0
- package/dist/platformAccessory.js +81 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/settings.d.ts +14 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +15 -0
- package/dist/settings.js.map +1 -0
- package/dist/stick/frame.d.ts +16 -0
- package/dist/stick/frame.d.ts.map +1 -0
- package/dist/stick/frame.js +51 -0
- package/dist/stick/frame.js.map +1 -0
- package/dist/stick/kdf.d.ts +23 -0
- package/dist/stick/kdf.d.ts.map +1 -0
- package/dist/stick/kdf.js +90 -0
- package/dist/stick/kdf.js.map +1 -0
- package/dist/stick/protocol.d.ts +28 -0
- package/dist/stick/protocol.d.ts.map +1 -0
- package/dist/stick/protocol.js +78 -0
- package/dist/stick/protocol.js.map +1 -0
- package/dist/stick/session.d.ts +50 -0
- package/dist/stick/session.d.ts.map +1 -0
- package/dist/stick/session.js +301 -0
- package/dist/stick/session.js.map +1 -0
- package/examples/dmx.yaml +65 -0
- package/package.json +51 -0
- package/src/cli/stick.ts +198 -0
- package/src/color/cct.ts +40 -0
- package/src/color/dimmer.ts +24 -0
- package/src/color/hsvcct.ts +59 -0
- package/src/color/index.ts +34 -0
- package/src/color/parsers.ts +232 -0
- package/src/color/rgb.ts +33 -0
- package/src/color/rgbaw.ts +46 -0
- package/src/color/rgbw.ts +36 -0
- package/src/color/rgbww.ts +56 -0
- package/src/color/types.ts +150 -0
- package/src/config.ts +248 -0
- package/src/controller.ts +136 -0
- package/src/index.ts +9 -0
- package/src/platform.ts +118 -0
- package/src/platformAccessory.ts +102 -0
- package/src/settings.ts +18 -0
- package/src/stick/frame.ts +70 -0
- package/src/stick/kdf.ts +99 -0
- package/src/stick/protocol.ts +90 -0
- package/src/stick/session.ts +348 -0
- package/stick-de3.json +6 -0
- package/tools/aes-hook.c +1404 -0
- package/tools/analyze-pcap.mjs +143 -0
- package/tools/capture-aes-key.lldb +25 -0
- package/tools/capture-and-verify.sh +46 -0
- package/tools/capture-handshake.sh +59 -0
- package/tools/capture-send.sh +39 -0
- package/tools/crack-kdf.mjs +121 -0
- package/tools/crack-key.mjs +170 -0
- package/tools/decode-frame.mjs +110 -0
- package/tools/decrypt-011c.mjs +123 -0
- package/tools/decrypt-dmx.mjs +130 -0
- package/tools/decrypt-ssl2.ts +154 -0
- package/tools/derive-dmx-key.mjs +189 -0
- package/tools/diff-011c.mjs +127 -0
- package/tools/dump-pcap-summary.mjs +54 -0
- package/tools/dump-tcp-stream.mjs +123 -0
- package/tools/extract-011c.mjs +117 -0
- package/tools/extract-frame.mjs +78 -0
- package/tools/find-011c-key.mjs +140 -0
- package/tools/find-iv.mjs +153 -0
- package/tools/ghidra/README.md +136 -0
- package/tools/ghidra/__pycache__/dump-011c-callers.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-011c-handler.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-aes-candidates.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-anchors.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-auth2.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-authenticate.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-block-cipher.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-cipher.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-ctor-chain.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-curve.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-encompassing.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-handshake.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-kdf-chain.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-kdf-context.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-profile.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-real-encrypt.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-secret.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-setsoftware.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-stick3-vtable.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-swxref.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-vt-key-getter.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-cipher.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-ctor.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-md5.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-secret-users.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3-crypto.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3-device.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3a-ctor.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3anet-ctor.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-vtables.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/string-walk.cpython-313.pyc +0 -0
- package/tools/ghidra/dump-011c-callers.py +140 -0
- package/tools/ghidra/dump-011c-handler.py +158 -0
- package/tools/ghidra/dump-aes-candidates.py +99 -0
- package/tools/ghidra/dump-anchors.py +192 -0
- package/tools/ghidra/dump-auth2.py +50 -0
- package/tools/ghidra/dump-authenticate.py +71 -0
- package/tools/ghidra/dump-block-cipher.py +88 -0
- package/tools/ghidra/dump-cipher.py +106 -0
- package/tools/ghidra/dump-ctor-chain.py +107 -0
- package/tools/ghidra/dump-curve.py +112 -0
- package/tools/ghidra/dump-encompassing.py +146 -0
- package/tools/ghidra/dump-handshake.py +76 -0
- package/tools/ghidra/dump-kdf-chain.py +105 -0
- package/tools/ghidra/dump-kdf-context.py +72 -0
- package/tools/ghidra/dump-profile.py +59 -0
- package/tools/ghidra/dump-real-encrypt.py +90 -0
- package/tools/ghidra/dump-secret.py +84 -0
- package/tools/ghidra/dump-setsoftware.py +63 -0
- package/tools/ghidra/dump-stick3-vtable.py +182 -0
- package/tools/ghidra/dump-swxref.py +27 -0
- package/tools/ghidra/dump-vt-key-getter.py +170 -0
- package/tools/ghidra/find-cipher.py +210 -0
- package/tools/ghidra/find-ctor.py +109 -0
- package/tools/ghidra/find-md5.py +97 -0
- package/tools/ghidra/find-secret-users.py +98 -0
- package/tools/ghidra/find-stick3-crypto.py +190 -0
- package/tools/ghidra/find-stick3-device.py +111 -0
- package/tools/ghidra/find-stick3a-ctor.py +87 -0
- package/tools/ghidra/find-stick3anet-ctor.py +76 -0
- package/tools/ghidra/find-vtables.py +165 -0
- package/tools/ghidra/out-011c-callers-run.log +51 -0
- package/tools/ghidra/out-011c-run.log +50 -0
- package/tools/ghidra/out-vt-key-run.log +55 -0
- package/tools/ghidra/string-walk.py +180 -0
- package/tools/hmac-key.sh +51 -0
- package/tools/hwm-entitlements.plist +18 -0
- package/tools/inspect-plaintext.mjs +87 -0
- package/tools/lldb-capture-aes.py +137 -0
- package/tools/lldb_hmac_key.py +93 -0
- package/tools/lldb_pathA_scan.py +341 -0
- package/tools/lldb_pathC_keytrace.py +300 -0
- package/tools/lldb_pathD_kdf_watch.py +281 -0
- package/tools/pathA-launch-hwm.sh +86 -0
- package/tools/pathA-scan.sh +52 -0
- package/tools/pathC-keytrace.sh +49 -0
- package/tools/pathC-run.sh +71 -0
- package/tools/pathD-kdf-watch.sh +51 -0
- package/tools/quick-trigger.mjs +86 -0
- package/tools/run-hooked-hwmanager.sh +51 -0
- package/tools/scan-aes-schedule.mjs +135 -0
- package/tools/send_dmx.mjs +480 -0
- package/tools/stick-status.mjs +57 -0
- package/tools/test-aes-detector.mjs +100 -0
- package/tools/test-tcp-write.ts +274 -0
- package/tools/test-udp-dmx.ts +324 -0
- package/tools/test-x25519.mjs +87 -0
- package/tools/try-decrypt.ts +144 -0
- package/tools/try-hardcoded-key.mjs +86 -0
- package/tools/try-hardcoded-key.ts +103 -0
- package/tools/try-key.mjs +103 -0
- package/tools/verify-011c-key.mjs +121 -0
- package/tools/verify-kdf.mjs +142 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// rgbww — RGB + WarmWhite + CoolWhite. The extracted white component
|
|
2
|
+
// is split between WW and CW based on the HomeKit CCT (mireds), falling
|
|
3
|
+
// back to 50/50 when CCT isn't set. WW channel optionally declares K=K
|
|
4
|
+
// (e.g. "WarmWhite 2700") and CW does the same; if absent, defaults
|
|
5
|
+
// 2700K (WW) / 6500K (CW) are used.
|
|
6
|
+
import { clamp, hsvToRgb01, rgbBytes, requireRoles, } from './types.js';
|
|
7
|
+
const DEFAULT_WW_K = 2700;
|
|
8
|
+
const DEFAULT_CW_K = 6500;
|
|
9
|
+
export const rgbww = {
|
|
10
|
+
id: 'rgbww',
|
|
11
|
+
characteristics: ['On', 'Brightness', 'Hue', 'Saturation', 'ColorTemperature'],
|
|
12
|
+
validate(channels) {
|
|
13
|
+
requireRoles(channels, ['red', 'green', 'blue', 'warm-white', 'cool-white']);
|
|
14
|
+
},
|
|
15
|
+
render(state, channels) {
|
|
16
|
+
const wwK = channels.find((c) => c.role === 'warm-white')?.kMin ?? DEFAULT_WW_K;
|
|
17
|
+
const cwK = channels.find((c) => c.role === 'cool-white')?.kMin ?? DEFAULT_CW_K;
|
|
18
|
+
const rgb01 = hsvToRgb01(state.hue ?? 0, state.saturation ?? 0);
|
|
19
|
+
const px = rgbBytes(rgb01, state);
|
|
20
|
+
const w = Math.min(px.r, px.g, px.b);
|
|
21
|
+
// Mix ratio: when CCT is at WW endpoint -> all WW; at CW endpoint -> all CW.
|
|
22
|
+
let warmFrac = 0.5;
|
|
23
|
+
if (state.colorTemperatureMireds != null && state.colorTemperatureMireds > 0) {
|
|
24
|
+
const k = 1_000_000 / state.colorTemperatureMireds;
|
|
25
|
+
warmFrac = clamp((cwK - k) / (cwK - wwK), 0, 1);
|
|
26
|
+
}
|
|
27
|
+
const ww = Math.round(w * warmFrac);
|
|
28
|
+
const cw = w - ww;
|
|
29
|
+
const out = new Uint8Array(channels.length);
|
|
30
|
+
for (let i = 0; i < channels.length; i++) {
|
|
31
|
+
if (channels[i].fine)
|
|
32
|
+
continue;
|
|
33
|
+
switch (channels[i].role) {
|
|
34
|
+
case 'red':
|
|
35
|
+
out[i] = px.r - w;
|
|
36
|
+
break;
|
|
37
|
+
case 'green':
|
|
38
|
+
out[i] = px.g - w;
|
|
39
|
+
break;
|
|
40
|
+
case 'blue':
|
|
41
|
+
out[i] = px.b - w;
|
|
42
|
+
break;
|
|
43
|
+
case 'warm-white':
|
|
44
|
+
out[i] = ww;
|
|
45
|
+
break;
|
|
46
|
+
case 'cool-white':
|
|
47
|
+
out[i] = cw;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=rgbww.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rgbww.js","sourceRoot":"","sources":["../../src/color/rgbww.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wEAAwE;AACxE,uEAAuE;AACvE,oEAAoE;AACpE,oCAAoC;AAEpC,OAAO,EAIL,KAAK,EACL,UAAU,EACV,QAAQ,EACR,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,MAAM,CAAC,MAAM,KAAK,GAAe;IAC/B,EAAE,EAAE,OAAO;IACX,eAAe,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,kBAAkB,CAAC;IAC9E,QAAQ,CAAC,QAAQ;QACf,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,CAAC,KAAwB,EAAE,QAAsB;QACrD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,IAAI,IAAI,YAAY,CAAC;QAChF,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,IAAI,IAAI,YAAY,CAAC;QAEhF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;QAChE,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAErC,6EAA6E;QAC7E,IAAI,QAAQ,GAAG,GAAG,CAAC;QACnB,IAAI,KAAK,CAAC,sBAAsB,IAAI,IAAI,IAAI,KAAK,CAAC,sBAAsB,GAAG,CAAC,EAAE,CAAC;YAC7E,MAAM,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC,sBAAsB,CAAC;YACnD,QAAQ,GAAG,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QAElB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;gBAAE,SAAS;YAC/B,QAAQ,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBACzB,KAAK,KAAK;oBAAU,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;oBAAC,MAAM;gBAC7C,KAAK,OAAO;oBAAQ,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;oBAAC,MAAM;gBAC7C,KAAK,MAAM;oBAAS,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;oBAAC,MAAM;gBAC7C,KAAK,YAAY;oBAAG,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;oBAAC,MAAM;gBACvC,KAAK,YAAY;oBAAG,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;oBAAC,MAAM;YACzC,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type HKCharacteristic = 'On' | 'Brightness' | 'Hue' | 'Saturation' | 'ColorTemperature';
|
|
2
|
+
/** Canonical HomeKit state. Optional fields are populated only for models
|
|
3
|
+
* that expose the corresponding characteristic. */
|
|
4
|
+
export interface HomeKitLightState {
|
|
5
|
+
on: boolean;
|
|
6
|
+
brightness: number;
|
|
7
|
+
hue?: number;
|
|
8
|
+
saturation?: number;
|
|
9
|
+
colorTemperatureMireds?: number;
|
|
10
|
+
}
|
|
11
|
+
export type ChannelRole = 'intensity' | 'cct' | 'saturation' | 'hue' | 'red' | 'green' | 'blue' | 'white' | 'warm-white' | 'cool-white' | 'amber';
|
|
12
|
+
/** One entry from a profile's `channel_order`. Produced by `parseChannelName`.
|
|
13
|
+
* `fine: true` means the byte at this slot is the low 8 bits of the 16-bit
|
|
14
|
+
* value paired with the immediately preceding non-fine entry of the same role. */
|
|
15
|
+
export interface ChannelDef {
|
|
16
|
+
role: ChannelRole;
|
|
17
|
+
fine?: boolean;
|
|
18
|
+
/** For CCT channels: Kelvin range. Required for CCT to mireds<->byte mapping. */
|
|
19
|
+
kMin?: number;
|
|
20
|
+
kMax?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ColorModel {
|
|
23
|
+
/** Lowercase identifier used in YAML/JSON config (`colormodel: hsvcct`). */
|
|
24
|
+
readonly id: string;
|
|
25
|
+
/** HomeKit characteristics this model wants on its fixture. */
|
|
26
|
+
readonly characteristics: HKCharacteristic[];
|
|
27
|
+
/** Throw if `channels` is inconsistent with this model's requirements. */
|
|
28
|
+
validate(channels: ChannelDef[]): void;
|
|
29
|
+
/** Produce a byte per `channels` entry, in order, given the HomeKit state. */
|
|
30
|
+
render(state: HomeKitLightState, channels: ChannelDef[]): Uint8Array;
|
|
31
|
+
/** Optional reverse direction; not implemented in v1. */
|
|
32
|
+
parse?(bytes: Uint8Array, channels: ChannelDef[]): HomeKitLightState;
|
|
33
|
+
}
|
|
34
|
+
/** Map HomeKit brightness (0..100) to a 16-bit value (0..65535) honoring On. */
|
|
35
|
+
export declare function brightness16(state: HomeKitLightState): number;
|
|
36
|
+
/** Map HomeKit brightness (0..100) to an 8-bit value (0..255) honoring On. */
|
|
37
|
+
export declare function brightness8(state: HomeKitLightState): number;
|
|
38
|
+
/** Mireds → Kelvin → 0..255 byte across [kMin, kMax]. */
|
|
39
|
+
export declare function ctByte(mireds: number | undefined, kMin: number, kMax: number): number;
|
|
40
|
+
/** HSV → RGB at V=1 (the brightness is applied separately on top — same idiom
|
|
41
|
+
* as unifi-ap-rgb so we don't double-darken). */
|
|
42
|
+
export declare function hsvToRgb01(hueDeg: number, satPct: number): {
|
|
43
|
+
r: number;
|
|
44
|
+
g: number;
|
|
45
|
+
b: number;
|
|
46
|
+
};
|
|
47
|
+
/** Scale 0..1 RGB by brightness (0..100) and on/off, return 0..255 bytes. */
|
|
48
|
+
export declare function rgbBytes(rgb01: {
|
|
49
|
+
r: number;
|
|
50
|
+
g: number;
|
|
51
|
+
b: number;
|
|
52
|
+
}, state: HomeKitLightState): {
|
|
53
|
+
r: number;
|
|
54
|
+
g: number;
|
|
55
|
+
b: number;
|
|
56
|
+
};
|
|
57
|
+
export declare function clamp(v: number, lo: number, hi: number): number;
|
|
58
|
+
/** Tag a single-byte channel index plus its fine companion (if any), and
|
|
59
|
+
* emit the right 16-bit split into `out`. Returns the index advanced past
|
|
60
|
+
* any fine companion. */
|
|
61
|
+
export declare function writeMaybe16(out: Uint8Array, channels: ChannelDef[], i: number, value16: number): number;
|
|
62
|
+
/** Validate that `roles` is exactly the set of (coarse) roles in `channels`. */
|
|
63
|
+
export declare function requireRoles(channels: ChannelDef[], roles: ChannelRole[]): void;
|
|
64
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/color/types.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,gBAAgB,GACxB,IAAI,GACJ,YAAY,GACZ,KAAK,GACL,YAAY,GACZ,kBAAkB,CAAC;AAEvB;oDACoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,KAAK,GACL,YAAY,GACZ,KAAK,GACL,KAAK,GACL,OAAO,GACP,MAAM,GACN,OAAO,GACP,YAAY,GACZ,YAAY,GACZ,OAAO,CAAC;AAEZ;;mFAEmF;AACnF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,eAAe,EAAE,gBAAgB,EAAE,CAAC;IAC7C,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IACvC,8EAA8E;IAC9E,MAAM,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAAC;IACrE,yDAAyD;IACzD,KAAK,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAAC;CACtE;AAID,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAI7D;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAG5D;AAED,yDAAyD;AACzD,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKrF;AAED;kDACkD;AAClD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAc9F;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,iBAAiB,GAC3F;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAOpC;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;0BAE0B;AAC1B,wBAAgB,YAAY,CAC1B,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,UAAU,EAAE,EACtB,CAAC,EAAE,MAAM,EACT,OAAO,EAAE,MAAM,GACd,MAAM,CASR;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,IAAI,CAY/E"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Color-model interfaces.
|
|
2
|
+
//
|
|
3
|
+
// A `ColorModel` translates the canonical HomeKit lightbulb state into the
|
|
4
|
+
// byte values a fixture expects, given that fixture's `channel_order` from
|
|
5
|
+
// the patch config. The optional `parse` is a placeholder for future
|
|
6
|
+
// state-read support; not implemented in v1.
|
|
7
|
+
// ── shared helpers ─────────────────────────────────────────────────────────
|
|
8
|
+
/** Map HomeKit brightness (0..100) to a 16-bit value (0..65535) honoring On. */
|
|
9
|
+
export function brightness16(state) {
|
|
10
|
+
if (!state.on)
|
|
11
|
+
return 0;
|
|
12
|
+
const pct = clamp(state.brightness, 0, 100);
|
|
13
|
+
return Math.round((pct / 100) * 65535);
|
|
14
|
+
}
|
|
15
|
+
/** Map HomeKit brightness (0..100) to an 8-bit value (0..255) honoring On. */
|
|
16
|
+
export function brightness8(state) {
|
|
17
|
+
if (!state.on)
|
|
18
|
+
return 0;
|
|
19
|
+
return Math.round((clamp(state.brightness, 0, 100) / 100) * 255);
|
|
20
|
+
}
|
|
21
|
+
/** Mireds → Kelvin → 0..255 byte across [kMin, kMax]. */
|
|
22
|
+
export function ctByte(mireds, kMin, kMax) {
|
|
23
|
+
if (mireds == null || mireds <= 0)
|
|
24
|
+
return 128;
|
|
25
|
+
const k = 1_000_000 / mireds;
|
|
26
|
+
const t = (k - kMin) / (kMax - kMin);
|
|
27
|
+
return Math.round(clamp(t, 0, 1) * 255);
|
|
28
|
+
}
|
|
29
|
+
/** HSV → RGB at V=1 (the brightness is applied separately on top — same idiom
|
|
30
|
+
* as unifi-ap-rgb so we don't double-darken). */
|
|
31
|
+
export function hsvToRgb01(hueDeg, satPct) {
|
|
32
|
+
const h = ((hueDeg % 360) + 360) % 360;
|
|
33
|
+
const s = clamp(satPct, 0, 100) / 100;
|
|
34
|
+
const c = s; // chroma when V = 1
|
|
35
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
36
|
+
let r1 = 0, g1 = 0, b1 = 0;
|
|
37
|
+
if (h < 60)
|
|
38
|
+
[r1, g1, b1] = [c, x, 0];
|
|
39
|
+
else if (h < 120)
|
|
40
|
+
[r1, g1, b1] = [x, c, 0];
|
|
41
|
+
else if (h < 180)
|
|
42
|
+
[r1, g1, b1] = [0, c, x];
|
|
43
|
+
else if (h < 240)
|
|
44
|
+
[r1, g1, b1] = [0, x, c];
|
|
45
|
+
else if (h < 300)
|
|
46
|
+
[r1, g1, b1] = [x, 0, c];
|
|
47
|
+
else
|
|
48
|
+
[r1, g1, b1] = [c, 0, x];
|
|
49
|
+
const m = 1 - c; // V - c with V = 1
|
|
50
|
+
return { r: r1 + m, g: g1 + m, b: b1 + m };
|
|
51
|
+
}
|
|
52
|
+
/** Scale 0..1 RGB by brightness (0..100) and on/off, return 0..255 bytes. */
|
|
53
|
+
export function rgbBytes(rgb01, state) {
|
|
54
|
+
const m = state.on ? clamp(state.brightness, 0, 100) / 100 : 0;
|
|
55
|
+
return {
|
|
56
|
+
r: Math.round(rgb01.r * m * 255),
|
|
57
|
+
g: Math.round(rgb01.g * m * 255),
|
|
58
|
+
b: Math.round(rgb01.b * m * 255),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function clamp(v, lo, hi) {
|
|
62
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
63
|
+
}
|
|
64
|
+
/** Tag a single-byte channel index plus its fine companion (if any), and
|
|
65
|
+
* emit the right 16-bit split into `out`. Returns the index advanced past
|
|
66
|
+
* any fine companion. */
|
|
67
|
+
export function writeMaybe16(out, channels, i, value16) {
|
|
68
|
+
const coarse = (value16 >> 8) & 0xff;
|
|
69
|
+
const fine = value16 & 0xff;
|
|
70
|
+
out[i] = coarse;
|
|
71
|
+
if (channels[i + 1]?.fine && channels[i + 1].role === channels[i].role) {
|
|
72
|
+
out[i + 1] = fine;
|
|
73
|
+
return i + 2;
|
|
74
|
+
}
|
|
75
|
+
return i + 1;
|
|
76
|
+
}
|
|
77
|
+
/** Validate that `roles` is exactly the set of (coarse) roles in `channels`. */
|
|
78
|
+
export function requireRoles(channels, roles) {
|
|
79
|
+
const present = channels.filter((c) => !c.fine).map((c) => c.role);
|
|
80
|
+
for (const r of roles) {
|
|
81
|
+
if (!present.includes(r)) {
|
|
82
|
+
throw new Error(`channel_order missing required role '${r}'`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const r of present) {
|
|
86
|
+
if (!roles.includes(r)) {
|
|
87
|
+
throw new Error(`channel_order has unexpected role '${r}' for this model`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/color/types.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ChannelDef, ColorModel } from './color/types.js';
|
|
2
|
+
export interface ControllerSpec {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
ip: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProfileSpec {
|
|
8
|
+
name: string;
|
|
9
|
+
colormodel: string;
|
|
10
|
+
channel_order: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface PatchSpec {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
controller?: string;
|
|
17
|
+
universe?: string;
|
|
18
|
+
start: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RawConfig {
|
|
21
|
+
name?: string;
|
|
22
|
+
yamlPath?: string;
|
|
23
|
+
controllers?: ControllerSpec[];
|
|
24
|
+
profiles?: ProfileSpec[];
|
|
25
|
+
patch?: PatchSpec[];
|
|
26
|
+
}
|
|
27
|
+
export interface Profile {
|
|
28
|
+
name: string;
|
|
29
|
+
model: ColorModel;
|
|
30
|
+
channels: ChannelDef[];
|
|
31
|
+
}
|
|
32
|
+
export declare const CONTROLLER_TYPES: readonly ["StickDE3"];
|
|
33
|
+
export type ControllerType = typeof CONTROLLER_TYPES[number];
|
|
34
|
+
export interface Controller {
|
|
35
|
+
id: string;
|
|
36
|
+
type: ControllerType;
|
|
37
|
+
ip: string;
|
|
38
|
+
}
|
|
39
|
+
export interface Fixture {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
profile: Profile;
|
|
43
|
+
controller: Controller;
|
|
44
|
+
universe: number;
|
|
45
|
+
startCh: number;
|
|
46
|
+
nChannels: number;
|
|
47
|
+
}
|
|
48
|
+
export interface LoadedConfig {
|
|
49
|
+
name: string;
|
|
50
|
+
controllers: Controller[];
|
|
51
|
+
fixtures: Fixture[];
|
|
52
|
+
}
|
|
53
|
+
export declare const DEFAULT_PLATFORM_NAME = "DMX";
|
|
54
|
+
/** Load + validate the platform config. `rawJson` is the Homebridge
|
|
55
|
+
* `platforms[]` entry; if it specifies `yamlPath`, that YAML file is loaded
|
|
56
|
+
* and treated as a full override of the JSON.
|
|
57
|
+
*
|
|
58
|
+
* If `cwd` is passed, relative yamlPath is resolved against it. */
|
|
59
|
+
export declare function loadConfig(rawJson: RawConfig, cwd?: string): LoadedConfig;
|
|
60
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAuCA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAI1D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,gBAAgB,uBAAwB,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;;;oEAIoE;AACpE,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CAyIzE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Config loader + validator.
|
|
2
|
+
//
|
|
3
|
+
// Inputs:
|
|
4
|
+
// - Homebridge config: `platforms[].DMX` block from Homebridge's
|
|
5
|
+
// config.json. Optional `yamlPath` points to an external YAML file
|
|
6
|
+
// whose contents take precedence over the JSON.
|
|
7
|
+
// - YAML override: same shape as the JSON block.
|
|
8
|
+
//
|
|
9
|
+
// Shape:
|
|
10
|
+
// {
|
|
11
|
+
// "platform": "DMX",
|
|
12
|
+
// "name": "DMX",
|
|
13
|
+
// "yamlPath": "/optional/path/to/dmx.yaml",
|
|
14
|
+
// "controllers": [
|
|
15
|
+
// { "id": "main", "type": "StickDE3", "ip": "192.168.96.2" }
|
|
16
|
+
// ],
|
|
17
|
+
// "profiles": [
|
|
18
|
+
// { "name": "WAC", "colormodel": "hsvcct",
|
|
19
|
+
// "channel_order": ["Intensity", "Intensity (Fine)",
|
|
20
|
+
// "ColorTemp 1650-8000", "Saturation", "Hue"] }
|
|
21
|
+
// ],
|
|
22
|
+
// "patch": [
|
|
23
|
+
// { "id": "a_down", "name": "A Down", "type": "WAC",
|
|
24
|
+
// "controller": "main", "universe": "A", "start": 6 }
|
|
25
|
+
// ]
|
|
26
|
+
// }
|
|
27
|
+
//
|
|
28
|
+
// Output: a normalized `LoadedConfig` the platform consumes. Validates:
|
|
29
|
+
// - controllers: unique ids, supported type, ip present
|
|
30
|
+
// - profiles: colormodel recognised, channel_order parses cleanly, model
|
|
31
|
+
// requirements satisfied
|
|
32
|
+
// - patch: unique ids, references a known profile + controller, valid
|
|
33
|
+
// universe ∈ {A,B}, start in [1, 512-nch+1], no slot overlap within a
|
|
34
|
+
// single (controller, universe).
|
|
35
|
+
import fs from 'node:fs';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
import yaml from 'js-yaml';
|
|
38
|
+
import { getColorModel } from './color/index.js';
|
|
39
|
+
import { parseChannelName } from './color/parsers.js';
|
|
40
|
+
export const CONTROLLER_TYPES = ['StickDE3'];
|
|
41
|
+
export const DEFAULT_PLATFORM_NAME = 'DMX';
|
|
42
|
+
/** Load + validate the platform config. `rawJson` is the Homebridge
|
|
43
|
+
* `platforms[]` entry; if it specifies `yamlPath`, that YAML file is loaded
|
|
44
|
+
* and treated as a full override of the JSON.
|
|
45
|
+
*
|
|
46
|
+
* If `cwd` is passed, relative yamlPath is resolved against it. */
|
|
47
|
+
export function loadConfig(rawJson, cwd) {
|
|
48
|
+
let raw = rawJson;
|
|
49
|
+
if (raw.yamlPath) {
|
|
50
|
+
const p = path.isAbsolute(raw.yamlPath) ? raw.yamlPath
|
|
51
|
+
: path.resolve(cwd ?? process.cwd(), raw.yamlPath);
|
|
52
|
+
const text = fs.readFileSync(p, 'utf8');
|
|
53
|
+
const parsed = yaml.load(text);
|
|
54
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
55
|
+
throw new Error(`yamlPath ${p}: not an object`);
|
|
56
|
+
}
|
|
57
|
+
raw = { ...rawJson, ...parsed };
|
|
58
|
+
}
|
|
59
|
+
// Controllers
|
|
60
|
+
const controllerSpecs = raw.controllers ?? [];
|
|
61
|
+
if (controllerSpecs.length === 0) {
|
|
62
|
+
throw new Error('config: at least one controller required under "controllers"');
|
|
63
|
+
}
|
|
64
|
+
const controllersById = new Map();
|
|
65
|
+
for (const cs of controllerSpecs) {
|
|
66
|
+
if (!cs.id)
|
|
67
|
+
throw new Error('controller: missing id');
|
|
68
|
+
if (controllersById.has(cs.id)) {
|
|
69
|
+
throw new Error(`controller: duplicate id "${cs.id}"`);
|
|
70
|
+
}
|
|
71
|
+
if (!CONTROLLER_TYPES.includes(cs.type)) {
|
|
72
|
+
throw new Error(`controller "${cs.id}": unknown type "${cs.type}". ` +
|
|
73
|
+
`Supported: ${CONTROLLER_TYPES.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
if (!cs.ip || typeof cs.ip !== 'string') {
|
|
76
|
+
throw new Error(`controller "${cs.id}": required field "ip" missing`);
|
|
77
|
+
}
|
|
78
|
+
controllersById.set(cs.id, { id: cs.id, type: cs.type, ip: cs.ip });
|
|
79
|
+
}
|
|
80
|
+
// Profiles
|
|
81
|
+
const profileSpecs = raw.profiles ?? [];
|
|
82
|
+
const profilesByName = new Map();
|
|
83
|
+
for (const ps of profileSpecs) {
|
|
84
|
+
if (!ps.name)
|
|
85
|
+
throw new Error('profile: missing name');
|
|
86
|
+
if (profilesByName.has(ps.name)) {
|
|
87
|
+
throw new Error(`profile: duplicate name "${ps.name}"`);
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(ps.channel_order) || ps.channel_order.length === 0) {
|
|
90
|
+
throw new Error(`profile "${ps.name}": channel_order must be a non-empty array`);
|
|
91
|
+
}
|
|
92
|
+
let model;
|
|
93
|
+
try {
|
|
94
|
+
model = getColorModel(ps.colormodel);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
throw new Error(`profile "${ps.name}": ${e.message}`);
|
|
98
|
+
}
|
|
99
|
+
let channels;
|
|
100
|
+
try {
|
|
101
|
+
channels = ps.channel_order.map(parseChannelName);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
throw new Error(`profile "${ps.name}" channel_order: ${e.message}`);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
model.validate(channels);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
throw new Error(`profile "${ps.name}": ${e.message}`);
|
|
111
|
+
}
|
|
112
|
+
profilesByName.set(ps.name, { name: ps.name, model, channels });
|
|
113
|
+
}
|
|
114
|
+
// Patch
|
|
115
|
+
const patchSpecs = raw.patch ?? [];
|
|
116
|
+
const fixtures = [];
|
|
117
|
+
const seenIds = new Set();
|
|
118
|
+
// Per controller + per universe: 512 channels, tracking which fixture
|
|
119
|
+
// owns each slot. Key is `${controllerId}:${universe}`.
|
|
120
|
+
const occupancy = new Map();
|
|
121
|
+
const slots = (cid, u) => {
|
|
122
|
+
const key = `${cid}:${u}`;
|
|
123
|
+
let m = occupancy.get(key);
|
|
124
|
+
if (!m) {
|
|
125
|
+
m = new Map();
|
|
126
|
+
occupancy.set(key, m);
|
|
127
|
+
}
|
|
128
|
+
return m;
|
|
129
|
+
};
|
|
130
|
+
for (const ps of patchSpecs) {
|
|
131
|
+
if (!ps.id)
|
|
132
|
+
throw new Error('patch: missing id');
|
|
133
|
+
if (seenIds.has(ps.id)) {
|
|
134
|
+
throw new Error(`patch: duplicate id "${ps.id}"`);
|
|
135
|
+
}
|
|
136
|
+
seenIds.add(ps.id);
|
|
137
|
+
const profile = profilesByName.get(ps.type);
|
|
138
|
+
if (!profile) {
|
|
139
|
+
throw new Error(`patch "${ps.id}": unknown profile "${ps.type}"`);
|
|
140
|
+
}
|
|
141
|
+
// controller reference. If only one controller is configured, default to it.
|
|
142
|
+
let controllerId = ps.controller;
|
|
143
|
+
if (!controllerId) {
|
|
144
|
+
if (controllersById.size === 1) {
|
|
145
|
+
controllerId = controllersById.keys().next().value;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
throw new Error(`patch "${ps.id}": "controller" required when more than one controller is configured`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const controller = controllersById.get(controllerId);
|
|
152
|
+
if (!controller) {
|
|
153
|
+
throw new Error(`patch "${ps.id}": unknown controller "${controllerId}"`);
|
|
154
|
+
}
|
|
155
|
+
const universeChar = (ps.universe ?? 'A').toUpperCase();
|
|
156
|
+
if (universeChar !== 'A' && universeChar !== 'B') {
|
|
157
|
+
throw new Error(`patch "${ps.id}": universe must be 'A' or 'B', got "${ps.universe}"`);
|
|
158
|
+
}
|
|
159
|
+
const universe = universeChar === 'A' ? 0 : 1;
|
|
160
|
+
const nch = profile.channels.length;
|
|
161
|
+
const start = Number(ps.start);
|
|
162
|
+
if (!Number.isInteger(start) || start < 1 || start + nch - 1 > 512) {
|
|
163
|
+
throw new Error(`patch "${ps.id}": start ${ps.start} + ${nch} channels does not fit in 1..512`);
|
|
164
|
+
}
|
|
165
|
+
const slotMap = slots(controllerId, universe);
|
|
166
|
+
for (let i = 0; i < nch; i++) {
|
|
167
|
+
const slot = start + i;
|
|
168
|
+
const owner = slotMap.get(slot);
|
|
169
|
+
if (owner) {
|
|
170
|
+
throw new Error(`patch "${ps.id}": DMX slot ${slot}@${universeChar} ` +
|
|
171
|
+
`(controller "${controllerId}") overlaps with "${owner}"`);
|
|
172
|
+
}
|
|
173
|
+
slotMap.set(slot, ps.id);
|
|
174
|
+
}
|
|
175
|
+
fixtures.push({
|
|
176
|
+
id: ps.id,
|
|
177
|
+
name: ps.name || ps.id,
|
|
178
|
+
profile,
|
|
179
|
+
controller,
|
|
180
|
+
universe,
|
|
181
|
+
startCh: start,
|
|
182
|
+
nChannels: nch,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
name: raw.name ?? DEFAULT_PLATFORM_NAME,
|
|
187
|
+
controllers: [...controllersById.values()],
|
|
188
|
+
fixtures,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,UAAU;AACV,mEAAmE;AACnE,uEAAuE;AACvE,oDAAoD;AACpD,mDAAmD;AACnD,EAAE;AACF,SAAS;AACT,MAAM;AACN,yBAAyB;AACzB,qBAAqB;AACrB,gDAAgD;AAChD,uBAAuB;AACvB,mEAAmE;AACnE,SAAS;AACT,oBAAoB;AACpB,iDAAiD;AACjD,6DAA6D;AAC7D,0EAA0E;AAC1E,SAAS;AACT,iBAAiB;AACjB,2DAA2D;AAC3D,8DAA8D;AAC9D,QAAQ;AACR,MAAM;AACN,EAAE;AACF,wEAAwE;AACxE,0DAA0D;AAC1D,2EAA2E;AAC3E,6BAA6B;AAC7B,wEAAwE;AACxE,0EAA0E;AAC1E,qCAAqC;AAErC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAqCtD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,UAAU,CAAU,CAAC;AAyBtD,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAE3C;;;;oEAIoE;AACpE,MAAM,UAAU,UAAU,CAAC,OAAkB,EAAE,GAAY;IACzD,IAAI,GAAG,GAAc,OAAO,CAAC;IAC7B,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjB,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ;YAC9C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAc,CAAC;QAC5C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;QAClD,CAAC;QACD,GAAG,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;IAClC,CAAC;IAED,cAAc;IACd,MAAM,eAAe,GAAG,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;IAC9C,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,eAAe,GAAG,IAAI,GAAG,EAAsB,CAAC;IACtD,KAAK,MAAM,EAAE,IAAI,eAAe,EAAE,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QACtD,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,6BAA6B,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACzD,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAsB,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,eAAe,EAAE,CAAC,EAAE,oBAAoB,EAAE,CAAC,IAAI,KAAK;gBACpD,cAAc,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5C,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,OAAO,EAAE,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC,EAAE,gCAAgC,CAAC,CAAC;QACxE,CAAC;QACD,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAsB,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,WAAW;IACX,MAAM,YAAY,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;IACxC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAmB,CAAC;IAClD,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACvD,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,4BAA4B,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,4CAA4C,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,KAAiB,CAAC;QACtB,IAAI,CAAC;YAAC,KAAK,GAAG,aAAa,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAAC,CAAC;QAC7C,OAAO,CAAC,EAAE,CAAC;YAAC,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,MAAO,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAAC,CAAC;QAC/E,IAAI,QAAsB,CAAC;QAC3B,IAAI,CAAC;YAAC,QAAQ,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAAC,CAAC;QAC1D,OAAO,CAAC,EAAE,CAAC;YAAC,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,oBAAqB,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAAC,CAAC;QAC7F,IAAI,CAAC;YAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAAC,CAAC;QACjC,OAAO,CAAC,EAAE,CAAC;YAAC,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,MAAO,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAAC,CAAC;QAC/E,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,QAAQ;IACR,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,sEAAsE;IACtE,wDAAwD;IACxD,MAAM,SAAS,GAAqC,IAAI,GAAG,EAAE,CAAC;IAC9D,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,CAAS,EAAuB,EAAE;QAC5D,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YAAC,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;YAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAAC,CAAC;QACjD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC,EAAE,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACjD,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,wBAAwB,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnB,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,uBAAuB,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC;QACpE,CAAC;QACD,6EAA6E;QAC7E,IAAI,YAAY,GAAG,EAAE,CAAC,UAAU,CAAC;QACjC,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,eAAe,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC/B,YAAY,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAe,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CACb,UAAU,EAAE,CAAC,EAAE,sEAAsE,CACtF,CAAC;YACJ,CAAC;QACH,CAAC;QACD,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,0BAA0B,YAAY,GAAG,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,YAAY,GAAG,CAAC,EAAE,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QACxD,IAAI,YAAY,KAAK,GAAG,IAAI,YAAY,KAAK,GAAG,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,wCAAwC,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,QAAQ,GAAG,YAAY,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;YACnE,MAAM,IAAI,KAAK,CACb,UAAU,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,KAAK,MAAM,GAAG,kCAAkC,CAC/E,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;YACvB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CACb,UAAU,EAAE,CAAC,EAAE,eAAe,IAAI,IAAI,YAAY,GAAG;oBACrD,gBAAgB,YAAY,qBAAqB,KAAK,GAAG,CAC1D,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3B,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE,EAAE,EAAE,CAAC,EAAE;YACT,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE;YACtB,OAAO;YACP,UAAU;YACV,QAAQ;YACR,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,GAAG;SACf,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,qBAAqB;QACvC,WAAW,EAAE,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC;QAC1C,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Logger } from 'homebridge';
|
|
2
|
+
import { Fixture } from './config.js';
|
|
3
|
+
import { HomeKitLightState } from './color/types.js';
|
|
4
|
+
export declare class StickController {
|
|
5
|
+
private readonly ip;
|
|
6
|
+
private readonly log;
|
|
7
|
+
private readonly debounceMs;
|
|
8
|
+
private targets;
|
|
9
|
+
private lastChangeAt;
|
|
10
|
+
private streaming;
|
|
11
|
+
/** Tracks whether the very first subprocess in this controller's
|
|
12
|
+
* lifetime should set RUN_PROBE=1. After a Stick power-cycle (which
|
|
13
|
+
* we can't detect in-band) the probe is required to (re-)register
|
|
14
|
+
* the client. Subsequent subprocesses can skip it. */
|
|
15
|
+
private needsProbe;
|
|
16
|
+
constructor(ip: string, log: Logger, debounceMs?: number);
|
|
17
|
+
/** Stage a fixture's bytes into the universe target buffer and ensure
|
|
18
|
+
* the streaming loop is running. */
|
|
19
|
+
setFixture(fixture: Fixture, state: HomeKitLightState): void;
|
|
20
|
+
private universeBuf;
|
|
21
|
+
/** Build the CLI args from current targets. Only non-zero channels are
|
|
22
|
+
* passed — send_dmx initialises the universe to 0 so omission == 0. */
|
|
23
|
+
private buildArgs;
|
|
24
|
+
/** Subprocess approach: spawn tools/send_dmx.mjs for each transaction.
|
|
25
|
+
* Fresh node process = no in-process state poisoning that breaks
|
|
26
|
+
* session 2+ in the long-lived plugin. Costs ~1.5-3s per spawn (process
|
|
27
|
+
* startup + handshake + 750ms stream + RST) but is empirically reliable. */
|
|
28
|
+
private run;
|
|
29
|
+
/** Shutdown hook. Subprocess approach has no long-lived state. */
|
|
30
|
+
shutdown(): void;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKzC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AASrD,qBAAa,eAAe;IAWxB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAZ7B,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B;;;2DAGuD;IACvD,OAAO,CAAC,UAAU,CAAQ;gBAGP,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAoB;IAGnD;yCACqC;IACrC,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAmB5D,OAAO,CAAC,WAAW;IAMnB;4EACwE;IACxE,OAAO,CAAC,SAAS;IAUjB;;;iFAG6E;YAC/D,GAAG;IA+CjB,kEAAkE;IAClE,QAAQ,IAAI,IAAI;CAGjB"}
|