@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.
Files changed (226) hide show
  1. package/CLAUDE.md +176 -0
  2. package/README.md +146 -0
  3. package/config.schema.json +143 -0
  4. package/dist/cli/stick.d.ts +3 -0
  5. package/dist/cli/stick.d.ts.map +1 -0
  6. package/dist/cli/stick.js +176 -0
  7. package/dist/cli/stick.js.map +1 -0
  8. package/dist/color/cct.d.ts +3 -0
  9. package/dist/color/cct.d.ts.map +1 -0
  10. package/dist/color/cct.js +33 -0
  11. package/dist/color/cct.js.map +1 -0
  12. package/dist/color/dimmer.d.ts +3 -0
  13. package/dist/color/dimmer.d.ts.map +1 -0
  14. package/dist/color/dimmer.js +16 -0
  15. package/dist/color/dimmer.js.map +1 -0
  16. package/dist/color/hsvcct.d.ts +3 -0
  17. package/dist/color/hsvcct.d.ts.map +1 -0
  18. package/dist/color/hsvcct.js +52 -0
  19. package/dist/color/hsvcct.js.map +1 -0
  20. package/dist/color/index.d.ts +7 -0
  21. package/dist/color/index.d.ts.map +1 -0
  22. package/dist/color/index.js +28 -0
  23. package/dist/color/index.js.map +1 -0
  24. package/dist/color/parsers.d.ts +20 -0
  25. package/dist/color/parsers.d.ts.map +1 -0
  26. package/dist/color/parsers.js +232 -0
  27. package/dist/color/parsers.js.map +1 -0
  28. package/dist/color/rgb.d.ts +3 -0
  29. package/dist/color/rgb.d.ts.map +1 -0
  30. package/dist/color/rgb.js +32 -0
  31. package/dist/color/rgb.js.map +1 -0
  32. package/dist/color/rgbaw.d.ts +3 -0
  33. package/dist/color/rgbaw.d.ts.map +1 -0
  34. package/dist/color/rgbaw.js +48 -0
  35. package/dist/color/rgbaw.js.map +1 -0
  36. package/dist/color/rgbw.d.ts +3 -0
  37. package/dist/color/rgbw.d.ts.map +1 -0
  38. package/dist/color/rgbw.js +37 -0
  39. package/dist/color/rgbw.js.map +1 -0
  40. package/dist/color/rgbww.d.ts +3 -0
  41. package/dist/color/rgbww.d.ts.map +1 -0
  42. package/dist/color/rgbww.js +54 -0
  43. package/dist/color/rgbww.js.map +1 -0
  44. package/dist/color/types.d.ts +64 -0
  45. package/dist/color/types.d.ts.map +1 -0
  46. package/dist/color/types.js +91 -0
  47. package/dist/color/types.js.map +1 -0
  48. package/dist/config.d.ts +60 -0
  49. package/dist/config.d.ts.map +1 -0
  50. package/dist/config.js +191 -0
  51. package/dist/config.js.map +1 -0
  52. package/dist/controller.d.ts +32 -0
  53. package/dist/controller.d.ts.map +1 -0
  54. package/dist/controller.js +128 -0
  55. package/dist/controller.js.map +1 -0
  56. package/dist/index.d.ts +4 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +7 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/platform.d.ts +17 -0
  61. package/dist/platform.d.ts.map +1 -0
  62. package/dist/platform.js +96 -0
  63. package/dist/platform.js.map +1 -0
  64. package/dist/platformAccessory.d.ts +18 -0
  65. package/dist/platformAccessory.d.ts.map +1 -0
  66. package/dist/platformAccessory.js +81 -0
  67. package/dist/platformAccessory.js.map +1 -0
  68. package/dist/settings.d.ts +14 -0
  69. package/dist/settings.d.ts.map +1 -0
  70. package/dist/settings.js +15 -0
  71. package/dist/settings.js.map +1 -0
  72. package/dist/stick/frame.d.ts +16 -0
  73. package/dist/stick/frame.d.ts.map +1 -0
  74. package/dist/stick/frame.js +51 -0
  75. package/dist/stick/frame.js.map +1 -0
  76. package/dist/stick/kdf.d.ts +23 -0
  77. package/dist/stick/kdf.d.ts.map +1 -0
  78. package/dist/stick/kdf.js +90 -0
  79. package/dist/stick/kdf.js.map +1 -0
  80. package/dist/stick/protocol.d.ts +28 -0
  81. package/dist/stick/protocol.d.ts.map +1 -0
  82. package/dist/stick/protocol.js +78 -0
  83. package/dist/stick/protocol.js.map +1 -0
  84. package/dist/stick/session.d.ts +50 -0
  85. package/dist/stick/session.d.ts.map +1 -0
  86. package/dist/stick/session.js +301 -0
  87. package/dist/stick/session.js.map +1 -0
  88. package/examples/dmx.yaml +65 -0
  89. package/package.json +51 -0
  90. package/src/cli/stick.ts +198 -0
  91. package/src/color/cct.ts +40 -0
  92. package/src/color/dimmer.ts +24 -0
  93. package/src/color/hsvcct.ts +59 -0
  94. package/src/color/index.ts +34 -0
  95. package/src/color/parsers.ts +232 -0
  96. package/src/color/rgb.ts +33 -0
  97. package/src/color/rgbaw.ts +46 -0
  98. package/src/color/rgbw.ts +36 -0
  99. package/src/color/rgbww.ts +56 -0
  100. package/src/color/types.ts +150 -0
  101. package/src/config.ts +248 -0
  102. package/src/controller.ts +136 -0
  103. package/src/index.ts +9 -0
  104. package/src/platform.ts +118 -0
  105. package/src/platformAccessory.ts +102 -0
  106. package/src/settings.ts +18 -0
  107. package/src/stick/frame.ts +70 -0
  108. package/src/stick/kdf.ts +99 -0
  109. package/src/stick/protocol.ts +90 -0
  110. package/src/stick/session.ts +348 -0
  111. package/stick-de3.json +6 -0
  112. package/tools/aes-hook.c +1404 -0
  113. package/tools/analyze-pcap.mjs +143 -0
  114. package/tools/capture-aes-key.lldb +25 -0
  115. package/tools/capture-and-verify.sh +46 -0
  116. package/tools/capture-handshake.sh +59 -0
  117. package/tools/capture-send.sh +39 -0
  118. package/tools/crack-kdf.mjs +121 -0
  119. package/tools/crack-key.mjs +170 -0
  120. package/tools/decode-frame.mjs +110 -0
  121. package/tools/decrypt-011c.mjs +123 -0
  122. package/tools/decrypt-dmx.mjs +130 -0
  123. package/tools/decrypt-ssl2.ts +154 -0
  124. package/tools/derive-dmx-key.mjs +189 -0
  125. package/tools/diff-011c.mjs +127 -0
  126. package/tools/dump-pcap-summary.mjs +54 -0
  127. package/tools/dump-tcp-stream.mjs +123 -0
  128. package/tools/extract-011c.mjs +117 -0
  129. package/tools/extract-frame.mjs +78 -0
  130. package/tools/find-011c-key.mjs +140 -0
  131. package/tools/find-iv.mjs +153 -0
  132. package/tools/ghidra/README.md +136 -0
  133. package/tools/ghidra/__pycache__/dump-011c-callers.cpython-313.pyc +0 -0
  134. package/tools/ghidra/__pycache__/dump-011c-handler.cpython-313.pyc +0 -0
  135. package/tools/ghidra/__pycache__/dump-aes-candidates.cpython-313.pyc +0 -0
  136. package/tools/ghidra/__pycache__/dump-anchors.cpython-313.pyc +0 -0
  137. package/tools/ghidra/__pycache__/dump-auth2.cpython-313.pyc +0 -0
  138. package/tools/ghidra/__pycache__/dump-authenticate.cpython-313.pyc +0 -0
  139. package/tools/ghidra/__pycache__/dump-block-cipher.cpython-313.pyc +0 -0
  140. package/tools/ghidra/__pycache__/dump-cipher.cpython-313.pyc +0 -0
  141. package/tools/ghidra/__pycache__/dump-ctor-chain.cpython-313.pyc +0 -0
  142. package/tools/ghidra/__pycache__/dump-curve.cpython-313.pyc +0 -0
  143. package/tools/ghidra/__pycache__/dump-encompassing.cpython-313.pyc +0 -0
  144. package/tools/ghidra/__pycache__/dump-handshake.cpython-313.pyc +0 -0
  145. package/tools/ghidra/__pycache__/dump-kdf-chain.cpython-313.pyc +0 -0
  146. package/tools/ghidra/__pycache__/dump-kdf-context.cpython-313.pyc +0 -0
  147. package/tools/ghidra/__pycache__/dump-profile.cpython-313.pyc +0 -0
  148. package/tools/ghidra/__pycache__/dump-real-encrypt.cpython-313.pyc +0 -0
  149. package/tools/ghidra/__pycache__/dump-secret.cpython-313.pyc +0 -0
  150. package/tools/ghidra/__pycache__/dump-setsoftware.cpython-313.pyc +0 -0
  151. package/tools/ghidra/__pycache__/dump-stick3-vtable.cpython-313.pyc +0 -0
  152. package/tools/ghidra/__pycache__/dump-swxref.cpython-313.pyc +0 -0
  153. package/tools/ghidra/__pycache__/dump-vt-key-getter.cpython-313.pyc +0 -0
  154. package/tools/ghidra/__pycache__/find-cipher.cpython-313.pyc +0 -0
  155. package/tools/ghidra/__pycache__/find-ctor.cpython-313.pyc +0 -0
  156. package/tools/ghidra/__pycache__/find-md5.cpython-313.pyc +0 -0
  157. package/tools/ghidra/__pycache__/find-secret-users.cpython-313.pyc +0 -0
  158. package/tools/ghidra/__pycache__/find-stick3-crypto.cpython-313.pyc +0 -0
  159. package/tools/ghidra/__pycache__/find-stick3-device.cpython-313.pyc +0 -0
  160. package/tools/ghidra/__pycache__/find-stick3a-ctor.cpython-313.pyc +0 -0
  161. package/tools/ghidra/__pycache__/find-stick3anet-ctor.cpython-313.pyc +0 -0
  162. package/tools/ghidra/__pycache__/find-vtables.cpython-313.pyc +0 -0
  163. package/tools/ghidra/__pycache__/string-walk.cpython-313.pyc +0 -0
  164. package/tools/ghidra/dump-011c-callers.py +140 -0
  165. package/tools/ghidra/dump-011c-handler.py +158 -0
  166. package/tools/ghidra/dump-aes-candidates.py +99 -0
  167. package/tools/ghidra/dump-anchors.py +192 -0
  168. package/tools/ghidra/dump-auth2.py +50 -0
  169. package/tools/ghidra/dump-authenticate.py +71 -0
  170. package/tools/ghidra/dump-block-cipher.py +88 -0
  171. package/tools/ghidra/dump-cipher.py +106 -0
  172. package/tools/ghidra/dump-ctor-chain.py +107 -0
  173. package/tools/ghidra/dump-curve.py +112 -0
  174. package/tools/ghidra/dump-encompassing.py +146 -0
  175. package/tools/ghidra/dump-handshake.py +76 -0
  176. package/tools/ghidra/dump-kdf-chain.py +105 -0
  177. package/tools/ghidra/dump-kdf-context.py +72 -0
  178. package/tools/ghidra/dump-profile.py +59 -0
  179. package/tools/ghidra/dump-real-encrypt.py +90 -0
  180. package/tools/ghidra/dump-secret.py +84 -0
  181. package/tools/ghidra/dump-setsoftware.py +63 -0
  182. package/tools/ghidra/dump-stick3-vtable.py +182 -0
  183. package/tools/ghidra/dump-swxref.py +27 -0
  184. package/tools/ghidra/dump-vt-key-getter.py +170 -0
  185. package/tools/ghidra/find-cipher.py +210 -0
  186. package/tools/ghidra/find-ctor.py +109 -0
  187. package/tools/ghidra/find-md5.py +97 -0
  188. package/tools/ghidra/find-secret-users.py +98 -0
  189. package/tools/ghidra/find-stick3-crypto.py +190 -0
  190. package/tools/ghidra/find-stick3-device.py +111 -0
  191. package/tools/ghidra/find-stick3a-ctor.py +87 -0
  192. package/tools/ghidra/find-stick3anet-ctor.py +76 -0
  193. package/tools/ghidra/find-vtables.py +165 -0
  194. package/tools/ghidra/out-011c-callers-run.log +51 -0
  195. package/tools/ghidra/out-011c-run.log +50 -0
  196. package/tools/ghidra/out-vt-key-run.log +55 -0
  197. package/tools/ghidra/string-walk.py +180 -0
  198. package/tools/hmac-key.sh +51 -0
  199. package/tools/hwm-entitlements.plist +18 -0
  200. package/tools/inspect-plaintext.mjs +87 -0
  201. package/tools/lldb-capture-aes.py +137 -0
  202. package/tools/lldb_hmac_key.py +93 -0
  203. package/tools/lldb_pathA_scan.py +341 -0
  204. package/tools/lldb_pathC_keytrace.py +300 -0
  205. package/tools/lldb_pathD_kdf_watch.py +281 -0
  206. package/tools/pathA-launch-hwm.sh +86 -0
  207. package/tools/pathA-scan.sh +52 -0
  208. package/tools/pathC-keytrace.sh +49 -0
  209. package/tools/pathC-run.sh +71 -0
  210. package/tools/pathD-kdf-watch.sh +51 -0
  211. package/tools/quick-trigger.mjs +86 -0
  212. package/tools/run-hooked-hwmanager.sh +51 -0
  213. package/tools/scan-aes-schedule.mjs +135 -0
  214. package/tools/send_dmx.mjs +480 -0
  215. package/tools/stick-status.mjs +57 -0
  216. package/tools/test-aes-detector.mjs +100 -0
  217. package/tools/test-tcp-write.ts +274 -0
  218. package/tools/test-udp-dmx.ts +324 -0
  219. package/tools/test-x25519.mjs +87 -0
  220. package/tools/try-decrypt.ts +144 -0
  221. package/tools/try-hardcoded-key.mjs +86 -0
  222. package/tools/try-hardcoded-key.ts +103 -0
  223. package/tools/try-key.mjs +103 -0
  224. package/tools/verify-011c-key.mjs +121 -0
  225. package/tools/verify-kdf.mjs +142 -0
  226. package/tsconfig.json +19 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kdf.js","sourceRoot":"","sources":["../../src/stick/kdf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { Buffer } from 'node:buffer';
2
+ export declare const TCP_PORT = 2431;
3
+ export declare const UDP_DST_PORT = 2431;
4
+ export declare const UDP_SRC_PORT = 2430;
5
+ export declare const MAGIC: Buffer<ArrayBuffer>;
6
+ export declare const LSAG: Buffer<ArrayBuffer>;
7
+ export declare const P0: Buffer<ArrayBuffer>;
8
+ export declare const AUTH_KEY: Buffer<ArrayBuffer>;
9
+ export declare const Q_FALLBACK: Buffer<ArrayBuffer>;
10
+ /** A monotonic 4-byte LE counter shared between TCP messages and UDP frame
11
+ * fieldA. The Stick rejects out-of-order tokens. */
12
+ export declare class Token {
13
+ private n;
14
+ constructor(start?: number);
15
+ next(): Buffer;
16
+ /** Current value without advancing. */
17
+ peek(): number;
18
+ }
19
+ /** Build a TCP/2431 message: magic(8) ‖ opcode(2 LE) ‖ ...body parts. */
20
+ export declare function msg(magic: Buffer, opcode: number, ...parts: Buffer[]): Buffer;
21
+ /** Find a Stick_3A / LSAG_ALL message of `opcode` and at least `minLen`
22
+ * bytes inside the accumulating receive buffer. Returns the slice or null. */
23
+ export declare function findMsg(rxBuf: Buffer, opcode: number, minLen: number): Buffer | null;
24
+ /** Poll a predicate every 20ms until it returns non-null or `timeoutMs`
25
+ * elapses. Returns the value or null on timeout. */
26
+ export declare function waitFor<T>(predicate: () => T | null, timeoutMs: number): Promise<T | null>;
27
+ export declare const sleep: (ms: number) => Promise<void>;
28
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/stick/protocol.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,YAAY,OAAO,CAAC;AACjC,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC,eAAO,MAAM,KAAK,qBAA0B,CAAC;AAC7C,eAAO,MAAM,IAAI,qBAA0B,CAAC;AAI5C,eAAO,MAAM,EAAE,qBAAyD,CAAC;AAKzE,eAAO,MAAM,QAAQ,qBAAuD,CAAC;AAM7E,eAAO,MAAM,UAAU,qBAItB,CAAC;AAEF;qDACqD;AACrD,qBAAa,KAAK;IAChB,OAAO,CAAC,CAAC,CAAS;gBACN,KAAK,SAAO;IACxB,IAAI,IAAI,MAAM;IAKd,uCAAuC;IACvC,IAAI,IAAI,MAAM;CACf;AAED,yEAAyE;AACzE,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAI7E;AAED;+EAC+E;AAC/E,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASpF;AAED;qDACqD;AACrD,wBAAgB,OAAO,CAAC,CAAC,EACvB,SAAS,EAAE,MAAM,CAAC,GAAG,IAAI,EACzB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAWnB;AAED,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CACR,CAAC"}
@@ -0,0 +1,78 @@
1
+ // Wire-protocol constants + low-level helpers for the Stick-DE3 control
2
+ // channel (TCP/2431) and the encrypted DMX stream (UDP/2431).
3
+ //
4
+ // Ported verbatim from tools/send_dmx.mjs (which is now frozen as the
5
+ // reference implementation). All offsets / magic / opcodes here have been
6
+ // reverse-engineered from the 2024-03-21 ESA2 HardwareManager binary; see
7
+ // the [[stick-cipher-is-stream-not-aes]] memory note for the full RE trail.
8
+ import { Buffer } from 'node:buffer';
9
+ export const TCP_PORT = 2431;
10
+ export const UDP_DST_PORT = 2431;
11
+ export const UDP_SRC_PORT = 2430;
12
+ export const MAGIC = Buffer.from('Stick_3A');
13
+ export const LSAG = Buffer.from('LSAG_ALL');
14
+ // 16-byte fixed plaintext header that every DMX frame starts with. Confirmed
15
+ // constant across ≥2 independent sessions. Recovered from decrypted frames.
16
+ export const P0 = Buffer.from('5b4e99da9685ad976c432b0a7ff9ffcc', 'hex');
17
+ // HMAC-SHA256 key for the 0x48 TCP-auth handshake.
18
+ // = "#h.6xcKsGD{y}-z" (15 bytes), runtime-extracted from HardwareManager
19
+ // and verified against a captured 0x48 message.
20
+ export const AUTH_KEY = Buffer.from('23682e3678634b7347447b797d2d7a', 'hex');
21
+ // The Stick's static 0x0F pubkey. Constant across every observed session
22
+ // (it's the "Q" the device claims as ephemeral but never actually rotates
23
+ // in practice). Used as a fallback if the live 0x0F reply can't be parsed.
24
+ // 64-byte little-endian wire form.
25
+ export const Q_FALLBACK = Buffer.from('87ef58c2660c272b54a74bbc94cb8518108e370b7eed78456bd8d120c6b9ac0a' +
26
+ 'd791e4ce698aea761679f4b92a3ecf2acd12bf9bc308ce0ba8cb96630871105e', 'hex');
27
+ /** A monotonic 4-byte LE counter shared between TCP messages and UDP frame
28
+ * fieldA. The Stick rejects out-of-order tokens. */
29
+ export class Token {
30
+ n;
31
+ constructor(start = 0x80) { this.n = start; }
32
+ next() {
33
+ const b = Buffer.alloc(8);
34
+ b.writeUInt32LE(this.n++, 0);
35
+ return b;
36
+ }
37
+ /** Current value without advancing. */
38
+ peek() { return this.n; }
39
+ }
40
+ /** Build a TCP/2431 message: magic(8) ‖ opcode(2 LE) ‖ ...body parts. */
41
+ export function msg(magic, opcode, ...parts) {
42
+ const op = Buffer.alloc(2);
43
+ op.writeUInt16LE(opcode, 0);
44
+ return Buffer.concat([magic, op, ...parts]);
45
+ }
46
+ /** Find a Stick_3A / LSAG_ALL message of `opcode` and at least `minLen`
47
+ * bytes inside the accumulating receive buffer. Returns the slice or null. */
48
+ export function findMsg(rxBuf, opcode, minLen) {
49
+ for (let o = 0; o + 10 <= rxBuf.length; o++) {
50
+ const head = rxBuf.subarray(o, o + 8);
51
+ if (!head.equals(MAGIC) && !head.equals(LSAG))
52
+ continue;
53
+ if (rxBuf.readUInt16LE(o + 8) !== opcode)
54
+ continue;
55
+ if (rxBuf.length < o + minLen)
56
+ continue;
57
+ return rxBuf.subarray(o, o + minLen);
58
+ }
59
+ return null;
60
+ }
61
+ /** Poll a predicate every 20ms until it returns non-null or `timeoutMs`
62
+ * elapses. Returns the value or null on timeout. */
63
+ export function waitFor(predicate, timeoutMs) {
64
+ return new Promise((resolve) => {
65
+ const deadline = Date.now() + timeoutMs;
66
+ const tick = () => {
67
+ const r = predicate();
68
+ if (r != null)
69
+ return resolve(r);
70
+ if (Date.now() > deadline)
71
+ return resolve(null);
72
+ setTimeout(tick, 20);
73
+ };
74
+ tick();
75
+ });
76
+ }
77
+ export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
78
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/stick/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import dgram from 'node:dgram';
2
+ export interface SessionOpts {
3
+ /** TCP/2431 control-channel target. */
4
+ port?: number;
5
+ /** Inter-message gap during handshake chatter (ms). Default 10. */
6
+ chatterMs?: number;
7
+ /** Settle after 0x2e enter-live before 0x10/0x11 (ms). Default 50. */
8
+ settle2eMs?: number;
9
+ /** Number of 0x70 sector reads (HWM-UI chatter). Default 0 = skip. */
10
+ sectors?: number;
11
+ /** Run HWM-style probe TCP connection before the live one. Default false. */
12
+ runProbe?: boolean;
13
+ /** Gap between probe close and go-live open (ms). Default 800. */
14
+ probeGapMs?: number;
15
+ /** Initial Token counter value. Default 0x80. */
16
+ tokenStart?: number;
17
+ /** Optional logger; default no-op. */
18
+ log?: (...args: unknown[]) => void;
19
+ /** Caller-owned UDP socket (bound to UDP_SRC_PORT 2430). When supplied,
20
+ * the session uses it for DMX sends + does NOT close it on destroy().
21
+ * Used by StickController to avoid the close-then-rebind cycle that
22
+ * empirically poisons the Stick into rejecting subsequent sessions
23
+ * inside a long-lived plugin process. */
24
+ udp?: dgram.Socket;
25
+ }
26
+ export declare class StickSession {
27
+ readonly ip: string;
28
+ private sock;
29
+ private udp;
30
+ /** When true, we own the UDP socket and must close it on destroy(). */
31
+ private ownsUdp;
32
+ private key;
33
+ private token;
34
+ private rxBuf;
35
+ private constructor();
36
+ /** Open the TCP/2431 control channel, run the handshake (auth → pre-DMX
37
+ * chatter → ECDH → optional sector reads → go-live), and return a session
38
+ * ready for `send()`. */
39
+ static connect(ip: string, opts?: SessionOpts): Promise<StickSession>;
40
+ /** Per-frame sequence counter (low byte). */
41
+ private seqCtr;
42
+ /** Send one encrypted DMX frame for `universe` (port=0 for A).
43
+ * `channels` must be a 512-byte Uint8Array. */
44
+ send(channels: Uint8Array, universe?: number): void;
45
+ /** Dirty-close: UDP close + TCP RST. The Stick latches the last DMX values
46
+ * it received (cf. memory note `stick-actually-latches-on-clean-disconnect`).
47
+ * Idempotent. */
48
+ destroy(): void;
49
+ }
50
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/stick/session.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,MAAM,YAAY,CAAC;AAmE/B,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnC;;;;8CAI0C;IAC1C,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC;CACpB;AAMD,qBAAa,YAAY;aAEL,EAAE,EAAE,MAAM;IAC1B,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,GAAG;IACX,uEAAuE;IACvE,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,KAAK;IARf,OAAO;IAWP;;8BAE0B;WACb,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;IAyJ/E,6CAA6C;IAC7C,OAAO,CAAC,MAAM,CAAK;IAEnB;oDACgD;IAChD,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,SAAI,GAAG,IAAI;IAW9C;;sBAEkB;IAClB,OAAO,IAAI,IAAI;CAMhB"}
@@ -0,0 +1,301 @@
1
+ // StickSession — TCP/2431 control channel + UDP/2431 encrypted DMX stream.
2
+ //
3
+ // Lifecycle:
4
+ // const session = await StickSession.connect(ip);
5
+ // session.send(channels, universe); // one DMX frame
6
+ // // ...repeat at 25 Hz...
7
+ // session.destroy(); // RST → Stick latches last values
8
+ //
9
+ // Ported from tools/send_dmx.mjs (frozen reference). Every env-var knob that
10
+ // existed in the .mjs is now a SessionOpts field with the same default; the
11
+ // env var remains as fallback so the CLI keeps its existing tuning behaviour.
12
+ import net from 'node:net';
13
+ import dgram from 'node:dgram';
14
+ import crypto from 'node:crypto';
15
+ import { Buffer } from 'node:buffer';
16
+ import { AUTH_KEY, LSAG, MAGIC, Q_FALLBACK, TCP_PORT, Token, UDP_DST_PORT, UDP_SRC_PORT, findMsg, msg, sleep, waitFor, } from './protocol.js';
17
+ // HWM-faithful UDP broadcasts. 4 magics × 3 bursts on UDP/2430, plus a
18
+ // "LIGHTINGSOFT_XHL Hardware Manager" announce on UDP/24299. Verified
19
+ // 2026-05-24: not optional — without them, sessions after a Stick
20
+ // power-cycle silently fail to actually drive output even though the
21
+ // TCP handshake reports go-live success.
22
+ const DISCOVERY_MAGICS = ['LSAG_ALL', 'Stick_U1', 'Stick_3A', 'Siudi_7B'];
23
+ const DISCOVERY_TAIL = Buffer.from('000014000000', 'hex');
24
+ async function discoveryBroadcast(udp) {
25
+ for (let burst = 0; burst < 3; burst++) {
26
+ for (const m of DISCOVERY_MAGICS) {
27
+ const pkt = Buffer.concat([Buffer.from(m, 'ascii'), DISCOVERY_TAIL]);
28
+ await new Promise((r) => udp.send(pkt, 2430, '255.255.255.255', () => r()));
29
+ }
30
+ await sleep(25);
31
+ }
32
+ }
33
+ const XHL_HEADER = Buffer.from('4c49474854494e47534f46545f58484c' +
34
+ '0000000000000000' +
35
+ '14000000' +
36
+ '01000000', 'hex');
37
+ async function announceHardwareManager(udp24299, instanceId) {
38
+ const body114 = Buffer.alloc(114 - 32);
39
+ instanceId.copy(body114, 0);
40
+ body114.writeUInt32LE(crypto.randomBytes(4).readUInt32LE(0), 8);
41
+ body114.writeUInt32LE(11, 12);
42
+ body114.write('Hardware Manager\0', 18, 'ascii');
43
+ const pkt114 = Buffer.concat([XHL_HEADER, body114]);
44
+ await new Promise((r) => udp24299.send(pkt114, 24299, '255.255.255.255', () => r()));
45
+ await sleep(25);
46
+ for (let i = 0; i < 2; i++) {
47
+ const tail = Buffer.concat([crypto.randomBytes(4), Buffer.from([0x0a + i * 2, 0])]);
48
+ const pkt46 = Buffer.concat([XHL_HEADER, instanceId, tail]);
49
+ await new Promise((r) => udp24299.send(pkt46, 24299, '255.255.255.255', () => r()));
50
+ await sleep(25);
51
+ }
52
+ }
53
+ import { deriveDmxKey, makeEphemeral, pointToWire, wireToPoint } from './kdf.js';
54
+ import { buildFrame } from './frame.js';
55
+ const env = (k) => process.env[k];
56
+ const envNum = (k, fallback) => env(k) != null ? Number(env(k)) : fallback;
57
+ export class StickSession {
58
+ ip;
59
+ sock;
60
+ udp;
61
+ ownsUdp;
62
+ key;
63
+ token;
64
+ rxBuf;
65
+ constructor(ip, sock, udp,
66
+ /** When true, we own the UDP socket and must close it on destroy(). */
67
+ ownsUdp, key, token, rxBuf) {
68
+ this.ip = ip;
69
+ this.sock = sock;
70
+ this.udp = udp;
71
+ this.ownsUdp = ownsUdp;
72
+ this.key = key;
73
+ this.token = token;
74
+ this.rxBuf = rxBuf;
75
+ }
76
+ /** Open the TCP/2431 control channel, run the handshake (auth → pre-DMX
77
+ * chatter → ECDH → optional sector reads → go-live), and return a session
78
+ * ready for `send()`. */
79
+ static async connect(ip, opts = {}) {
80
+ const resolved = {
81
+ port: opts.port ?? TCP_PORT,
82
+ chatterMs: opts.chatterMs ?? envNum('CHATTER_MS', 10),
83
+ settle2eMs: opts.settle2eMs ?? envNum('SETTLE_2E_MS', 50),
84
+ sectors: opts.sectors ?? envNum('SECTORS', 0),
85
+ runProbe: opts.runProbe ?? env('RUN_PROBE') === '1',
86
+ probeGapMs: opts.probeGapMs ?? envNum('PROBE_GAP_MS', 800),
87
+ tokenStart: opts.tokenStart ?? 0x80,
88
+ log: opts.log ?? (() => undefined),
89
+ };
90
+ const log = resolved.log;
91
+ const token = new Token(resolved.tokenStart);
92
+ // UDP socket for the DMX stream + the HWM-faithful broadcasts. If the
93
+ // caller (the controller, when running in a long-lived plugin process)
94
+ // owns one, we reuse it; otherwise we create one for this session and
95
+ // close it on destroy(). See the comment on SessionOpts.udp.
96
+ let udp;
97
+ let ownsUdp = false;
98
+ if (opts.udp) {
99
+ udp = opts.udp;
100
+ }
101
+ else {
102
+ ownsUdp = true;
103
+ udp = dgram.createSocket({ type: 'udp4', reuseAddr: true });
104
+ await new Promise((res, rej) => {
105
+ udp.once('error', rej);
106
+ udp.bind(UDP_SRC_PORT, () => { udp.setBroadcast(true); res(); });
107
+ });
108
+ }
109
+ await discoveryBroadcast(udp);
110
+ const udp24299 = dgram.createSocket('udp4');
111
+ try {
112
+ await new Promise((res, rej) => {
113
+ udp24299.once('error', rej);
114
+ udp24299.bind(24299, () => { udp24299.setBroadcast(true); res(); });
115
+ });
116
+ const instanceId = crypto.randomBytes(8);
117
+ await announceHardwareManager(udp24299, instanceId);
118
+ }
119
+ finally {
120
+ udp24299.close();
121
+ }
122
+ await sleep(200);
123
+ if (resolved.runProbe) {
124
+ const probe = await openTcp(ip, resolved.port);
125
+ try {
126
+ const probeBuf = { v: Buffer.alloc(0) };
127
+ probe.on('data', (d) => { probeBuf.v = Buffer.concat([probeBuf.v, d]); });
128
+ await doAuth(probe, () => probeBuf.v, token, '[probe]', log);
129
+ // 0x00 → 0xc9 status registration
130
+ probe.write(msg(MAGIC, 0x00, Buffer.from('14000000', 'hex')));
131
+ await waitFor(() => findMsg(probeBuf.v, 0x00c9, 18), 1500);
132
+ // 0x011c — we know this is just AES-encrypted device-info; discard reply
133
+ probe.write(msg(MAGIC, 0x011c, token.next(), Buffer.from('01001600', 'hex')));
134
+ await waitFor(() => findMsg(probeBuf.v, 0x011c, 22), 2000);
135
+ // 0x007b — license query
136
+ probe.write(msg(MAGIC, 0x007b, token.next()));
137
+ await waitFor(() => findMsg(probeBuf.v, 0x007b, 22), 1500);
138
+ await sleep(150);
139
+ }
140
+ finally {
141
+ await new Promise((res) => probe.end(() => res()));
142
+ }
143
+ await sleep(resolved.probeGapMs);
144
+ }
145
+ const sock = await openTcp(ip, resolved.port);
146
+ const rxBuf = { v: Buffer.alloc(0) };
147
+ sock.on('data', (d) => { rxBuf.v = Buffer.concat([rxBuf.v, d]); });
148
+ await doAuth(sock, () => rxBuf.v, token, '[live]', log);
149
+ // Pre-DMX chatter — match send_dmx.mjs EXACTLY (which is empirically
150
+ // known to work multi-session from fresh processes). Earlier attempt
151
+ // to wait explicitly for 0xc9 in the middle may have been wrong.
152
+ for (const m of [
153
+ msg(MAGIC, 0x46, Buffer.alloc(4)),
154
+ msg(MAGIC, 0x09, Buffer.from('14000000', 'hex')),
155
+ msg(MAGIC, 0x09, Buffer.from('14000000', 'hex')),
156
+ msg(MAGIC, 0x00, Buffer.from('14000000', 'hex')),
157
+ msg(MAGIC, 0x011c, token.next(), Buffer.from('01001600', 'hex')),
158
+ msg(MAGIC, 0x05, Buffer.from('0200', 'hex')),
159
+ ]) {
160
+ sock.write(m);
161
+ await sleep(resolved.chatterMs);
162
+ }
163
+ if (findMsg(rxBuf.v, 0x00c9, 18))
164
+ log('0xc9 status received — session registered');
165
+ // 0x10 — crypto-state query
166
+ rxBuf.v = Buffer.alloc(0);
167
+ sock.write(msg(MAGIC, 0x10, token.next()));
168
+ const r10 = await waitFor(() => findMsg(rxBuf.v, 0x10, 22), 2000);
169
+ if (r10)
170
+ log(`0x10 crypto state: ${r10.readUInt32LE(0x12)}`);
171
+ rxBuf.v = Buffer.alloc(0);
172
+ // 0x0F — ECDH pubkey exchange
173
+ const ecdh = makeEphemeral();
174
+ const ourP256 = pointToWire(ecdh.getPublicKey(null, 'uncompressed'));
175
+ sock.write(msg(MAGIC, 0x0f, token.next(), ourP256));
176
+ const r0f = await waitFor(() => findMsg(rxBuf.v, 0x0f, 86), 3000);
177
+ let Qwire = Q_FALLBACK;
178
+ if (r0f != null) {
179
+ Qwire = r0f.subarray(0x16, 0x56);
180
+ log('0x0F ok — got Stick DMX pubkey');
181
+ }
182
+ else {
183
+ log('0x0F — no reply, using static Stick key');
184
+ }
185
+ const key = deriveDmxKey(ecdh, wireToPoint(Qwire));
186
+ log(`DMX key derived: ${key.toString('hex')}`);
187
+ // 6b. device sync — replicated byte-for-byte against a captured HWM session
188
+ sock.write(msg(MAGIC, 0x10, token.next()));
189
+ await sleep(resolved.chatterMs);
190
+ sock.write(msg(MAGIC, 0x75, token.next()));
191
+ await sleep(resolved.chatterMs);
192
+ sock.write(msg(MAGIC, 0x74, token.next()));
193
+ await sleep(resolved.chatterMs);
194
+ for (const p of ['0200000000', '0100000000', '0100000000', '02b37f0000']) {
195
+ sock.write(msg(MAGIC, 0x71, token.next(), Buffer.from(p, 'hex')));
196
+ await sleep(resolved.chatterMs);
197
+ }
198
+ // 0x70 sector reads — HWM commissioning-UI chatter; user-confirmed not
199
+ // required for go-live. Default 0; set SECTORS=N or sectors:N to include.
200
+ if (resolved.sectors > 0) {
201
+ const full = [0];
202
+ for (let s = 63; s <= 185; s++)
203
+ full.push(s);
204
+ const list = full.slice(0, resolved.sectors);
205
+ log(`0x70 device download (${list.length} sectors)`);
206
+ for (let i = 0; i < list.length; i++) {
207
+ const body = Buffer.alloc(5);
208
+ body.writeUInt32LE(list[i], 0);
209
+ body[4] = 1;
210
+ sock.write(msg(MAGIC, 0x70, token.next(), body));
211
+ await sleep(12);
212
+ if ((i & 0x1f) === 0x1f)
213
+ rxBuf.v = Buffer.alloc(0);
214
+ }
215
+ await sleep(200);
216
+ }
217
+ rxBuf.v = Buffer.alloc(0);
218
+ // Enter live mode: 0x2e, settle, 0x10/0x11/0x10
219
+ sock.write(msg(MAGIC, 0x2e, Buffer.alloc(32)));
220
+ await sleep(resolved.chatterMs);
221
+ await sleep(resolved.settle2eMs);
222
+ sock.write(msg(MAGIC, 0x10, token.next()));
223
+ await sleep(resolved.chatterMs);
224
+ sock.write(msg(MAGIC, 0x11, token.next()));
225
+ const r11 = await waitFor(() => findMsg(rxBuf.v, 0x11, 22), 3000);
226
+ log(r11 ? 'live mode enabled (0x11 ok)' : '0x11 — no reply');
227
+ sock.write(msg(MAGIC, 0x10, token.next()));
228
+ const r10b = await waitFor(() => findMsg(rxBuf.v, 0x10, 22), 1500);
229
+ if (r10b)
230
+ log(`0x10 after 0x11 — crypto state ${r10b.readUInt32LE(0x12)}`);
231
+ return new StickSession(ip, sock, udp, ownsUdp, key, token, rxBuf.v);
232
+ }
233
+ /** Per-frame sequence counter (low byte). */
234
+ seqCtr = 0;
235
+ /** Send one encrypted DMX frame for `universe` (port=0 for A).
236
+ * `channels` must be a 512-byte Uint8Array. */
237
+ send(channels, universe = 0) {
238
+ const frame = buildFrame({
239
+ key: this.key,
240
+ channels,
241
+ port: universe,
242
+ fieldA: this.token.next(),
243
+ seq: this.seqCtr++,
244
+ });
245
+ this.udp.send(frame, UDP_DST_PORT, this.ip, () => { });
246
+ }
247
+ /** Dirty-close: UDP close + TCP RST. The Stick latches the last DMX values
248
+ * it received (cf. memory note `stick-actually-latches-on-clean-disconnect`).
249
+ * Idempotent. */
250
+ destroy() {
251
+ if (this.ownsUdp) {
252
+ try {
253
+ this.udp.close();
254
+ }
255
+ catch { /* already closed */ }
256
+ }
257
+ try {
258
+ this.sock.destroy();
259
+ }
260
+ catch { /* already destroyed */ }
261
+ }
262
+ }
263
+ // ── internals ──────────────────────────────────────────────────────────────
264
+ function openTcp(ip, port) {
265
+ return new Promise((res, rej) => {
266
+ const s = net.createConnection({ host: ip, port });
267
+ s.setTimeout(5000);
268
+ s.once('connect', () => { s.setTimeout(0); res(s); });
269
+ s.once('error', rej);
270
+ s.once('timeout', () => rej(new Error(`no response from ${ip}:${port}`)));
271
+ });
272
+ }
273
+ async function doAuth(sock, getBuf, token, tag, log) {
274
+ // 0x47 — LSAG_ALL hello; Stick replies with its 32-byte handshake key
275
+ sock.write(msg(LSAG, 0x47, token.next()));
276
+ const r47 = await waitFor(() => findMsg(getBuf(), 0x47, 54), 3000);
277
+ let stickKey = Buffer.alloc(32);
278
+ if (r47 != null) {
279
+ stickKey = Buffer.from(r47.subarray(0x16, 0x36));
280
+ log(`${tag} 0x47 ok`);
281
+ }
282
+ else {
283
+ log(`${tag} 0x47 — no reply`);
284
+ }
285
+ // 0x48 — authenticated handshake (HMAC-SHA256 of the 82-byte head)
286
+ const software = Buffer.alloc(32);
287
+ software.write('software');
288
+ const head48 = msg(LSAG, 0x48, token.next(), software, stickKey);
289
+ const mac48 = crypto.createHmac('sha256', AUTH_KEY).update(head48).digest();
290
+ sock.write(Buffer.concat([head48, mac48]));
291
+ const r48 = await waitFor(() => findMsg(getBuf(), 0x48, 22), 2000);
292
+ if (r48) {
293
+ const st = r48.readUInt32LE(0x12);
294
+ log(`${tag} 0x48 auth status: ${st}${st === 0 ? ' (ok)' : ' (REJECTED)'}`);
295
+ }
296
+ else {
297
+ log(`${tag} 0x48 — no reply`);
298
+ }
299
+ return stickKey;
300
+ }
301
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/stick/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ # Example homebridge-dmx config.
2
+ #
3
+ # Save this to e.g. /opt/containers/homebridge/dmx.yaml (path visible inside
4
+ # the container as /homebridge/dmx.yaml) and reference it from Homebridge's
5
+ # config.json with:
6
+ #
7
+ # "platforms": [{
8
+ # "platform": "DMX",
9
+ # "yamlPath": "/homebridge/dmx.yaml"
10
+ # }]
11
+
12
+ controllers:
13
+ # One entry per physical DMX controller. Currently only StickDE3 is
14
+ # supported; the abstraction is here so other controller types can be
15
+ # added later without rewriting your patch.
16
+ - id: main
17
+ type: StickDE3
18
+ ip: 192.168.96.2
19
+
20
+ profiles:
21
+ # Fixture types. Each profile names a color model + the order its DMX
22
+ # channels appear in. "(Fine)" makes a channel the low byte of a 16-bit
23
+ # companion to the preceding channel. CCT channels carry their K range.
24
+ - name: WAC
25
+ colormodel: hsvcct
26
+ channel_order:
27
+ - Intensity
28
+ - Intensity (Fine)
29
+ - ColorTemp 1650-8000
30
+ - Saturation
31
+ - Hue
32
+
33
+ - name: CustomRGB
34
+ colormodel: rgb
35
+ channel_order:
36
+ - Blue
37
+ - Green
38
+ - Red
39
+
40
+ - name: Dimmer
41
+ colormodel: dimmer
42
+ channel_order:
43
+ - Intensity
44
+
45
+ patch:
46
+ # One entry per physical fixture. `controller` is optional when only one
47
+ # controller is configured. `universe` defaults to A.
48
+ - id: a_down
49
+ name: A Down
50
+ type: WAC
51
+ controller: main
52
+ universe: A
53
+ start: 6
54
+
55
+ - id: post_light
56
+ name: Post Light
57
+ type: CustomRGB
58
+ controller: main
59
+ start: 100
60
+
61
+ - id: shed_dimmer
62
+ name: Shed Dimmer
63
+ type: Dimmer
64
+ controller: main
65
+ start: 200
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@ecopoesis/homebridge-dmx",
3
+ "displayName": "DMX",
4
+ "version": "0.1.0",
5
+ "description": "Homebridge platform for DMX controllers (currently Nicolaudie Stick-DE3)",
6
+ "license": "Apache-2.0",
7
+ "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ecopoesis/homebridge-dmx.git"
11
+ },
12
+ "engines": {
13
+ "node": ">=18.15.0",
14
+ "homebridge": ">=1.5.0"
15
+ },
16
+ "main": "dist/index.js",
17
+ "scripts": {
18
+ "build": "rimraf ./dist && tsc",
19
+ "watch": "npm run build && npm link && nodemon",
20
+ "stick": "npm run build && node dist/cli/stick.js"
21
+ },
22
+ "keywords": [
23
+ "homebridge-plugin",
24
+ "dmx",
25
+ "nicolaudie",
26
+ "stick-de3",
27
+ "wac"
28
+ ],
29
+ "devDependencies": {
30
+ "@types/node": "^18.0.0",
31
+ "homebridge": "^1.5.0",
32
+ "rimraf": "^3.0.2",
33
+ "typescript": "^5.0.0",
34
+ "@types/js-yaml": "^4.0.9"
35
+ },
36
+ "types": "./dist/index.d.ts",
37
+ "author": {
38
+ "name": "Mike Roberts",
39
+ "email": "miker@miker.org",
40
+ "url": "https://github.com/ecopoesis"
41
+ },
42
+ "dependencies": {
43
+ "js-yaml": "^4.1.1"
44
+ },
45
+ "bin": {
46
+ "stick": "dist/cli/stick.js"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }