@ecopoesis/homebridge-dmx 0.3.0 → 0.4.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 CHANGED
@@ -11,6 +11,7 @@ Published to npm as **`@ecopoesis/homebridge-dmx`** (scoped because the unscoped
11
11
  - HomeKit set → render bytes via the fixture's color model → write to universe buffer → debounce (750 ms) → spawn `tools/send_dmx.mjs` as a child process per transaction
12
12
  - Subprocess approach: empirical workaround — in-process protocol only works for the FIRST transaction per long-lived plugin process; sessions 2+ silently fail to drive output. Fresh node process per transaction reliably works. Cost: ~1.5-2 s per change.
13
13
  - Color models: dimmer, cct, rgb, rgbw, rgbww, rgbaw, hsvcct (the WAC native model)
14
+ - Keepalive refresh (0.4.0): per-controller `refreshMinutes` (default 30, 0 = off) re-sends the last-known state after that much idle time. Reason: the Stick drifts into a wedged state after hours without traffic — sessions + crypto succeed but live output is dark (2026-08-22..25); regular traffic prevents/corrects it and re-asserts state after the nightly 4:30 AM ET PoE power-cycle (systemd user timer on homebridge01 → UniFi API poe_mode off/auto on sw01 port 15; see `tools/stick-power-cycle.py`). The Stick also has a ~3 min post-boot grace period where sessions succeed but output is ignored.
14
15
  - `tools/send_dmx.mjs` is now structurally critical (frozen reference shipped in the npm tarball)
15
16
 
16
17
  ## Current deployment
package/ENCRYPTION.md ADDED
@@ -0,0 +1,207 @@
1
+ # Stick-DE3 Encryption
2
+
3
+ All of the encryption used by the Stick-DE3 / Hardware Manager protocol,
4
+ recovered by reverse engineering and verified end-to-end against captured
5
+ sessions.
6
+
7
+ Two independent crypto primitives are involved:
8
+
9
+ 1. **HMAC-SHA256** — TCP auth handshake (opcode 0x48). Fixed key.
10
+ 2. **AES-256-CBC** with a P-256 ECDH-derived per-session key — the DMX UDP
11
+ stream and the 0x011c control reply.
12
+
13
+ For the wire format that uses these, see [PROTOCOL.md](PROTOCOL.md). For the
14
+ working implementation, see [`tools/send_dmx.mjs`](tools/send_dmx.mjs) and
15
+ [`tools/derive-dmx-key.mjs`](tools/derive-dmx-key.mjs).
16
+
17
+ ## 1. TCP auth — HMAC-SHA256
18
+
19
+ The opcode 0x48 message authenticates the client to the Stick. The signed
20
+ region is the 82-byte head of the message (everything up to and not
21
+ including the trailing HMAC):
22
+
23
+ ```
24
+ HMAC-SHA256(AUTH_KEY, magic(8) ‖ 0x48 ‖ token(8) ‖ softwareName(32) ‖ stickHandshakeKey(32))
25
+ ```
26
+
27
+ The HMAC is appended (32 bytes) to make the full 0x48 message 114 bytes.
28
+
29
+ ### `AUTH_KEY`
30
+
31
+ A 15-byte ASCII string baked into Hardware Manager. The literal string is:
32
+
33
+ ```
34
+ #h.6xcKsGD{y}-z
35
+ ```
36
+
37
+ As bytes (hex):
38
+
39
+ ```
40
+ 23 68 2e 36 78 63 4b 73 47 44 7b 79 7d 2d 7a
41
+ ```
42
+
43
+ Embedded as a constant in [`tools/send_dmx.mjs`](tools/send_dmx.mjs#L45) as
44
+ `AUTH_KEY`. Originally extracted at runtime with `tools/hmac-key.sh` /
45
+ `tools/lldb_hmac_key.py`, then verified against a captured HWM handshake.
46
+
47
+ A bad HMAC results in 0x48 reply status 100 (`PermissionDenied`), and the
48
+ session is never promoted to a live control session — no further opcodes will
49
+ work.
50
+
51
+ ## 2. DMX stream cipher — AES-256-CBC, P-256-derived key
52
+
53
+ The 544-byte body of every DMX UDP frame is AES-256 in CBC mode. The IV is
54
+ constructed from two fields in the clear 32-byte header
55
+ (`fieldA ‖ nonce`, see [PROTOCOL.md](PROTOCOL.md#aes-iv)). The key is
56
+ derived freshly each session from an ECDH-style handshake on opcode 0x0F.
57
+
58
+ ### Mode confirmation
59
+
60
+ The cipher was statically reverse-engineered from the 2024-03-21 Hardware
61
+ Manager binary: custom-inlined AES (S-box at `DAT_1007c0010`), not the
62
+ Gladman library reference. The mode bit in the cipher state structure
63
+ selects CBC or CFB; the live Stick-DE3 path uses **CBC**, confirmed by
64
+ re-encrypting a captured plaintext with the recovered key + IV and
65
+ byte-matching the captured ciphertext.
66
+
67
+ ### The KDF (`deriveDmxKey`)
68
+
69
+ Implemented in [`tools/derive-dmx-key.mjs`](tools/derive-dmx-key.mjs).
70
+ Recovered from `FUN_100107650` in the binary. In full:
71
+
72
+ ```
73
+ 1. d = random scalar mod n (client ephemeral private key)
74
+ 2. our = d · G (client ephemeral public key)
75
+ 3. send `our`, receive Stick pubkey `Q` (opcode 0x0F handshake)
76
+ 4. S = decompress(STATIC_POINT_COMPRESSED)
77
+ 5. AES-256 key = LE( X(d · S) XOR X(d · Q) )
78
+ ```
79
+
80
+ This is a **two-DH construction**: one ECDH against the hardcoded static
81
+ point `S` (authenticates the application — anyone who doesn't know `S`
82
+ can't talk to the Stick), and one against the Stick's per-session ephemeral
83
+ `Q` (provides forward secrecy across sessions).
84
+
85
+ The Stick computes the identical key on its side as
86
+ `X(s · our) XOR X(e · our)`, where `s` and `e` are the Stick's static and
87
+ ephemeral private scalars. The two agree by ECDH symmetry.
88
+
89
+ ### Curve parameters
90
+
91
+ NIST P-256 / secp256r1 / prime256v1. Confirmed three ways from the binary —
92
+ prime, order, and base point all byte-match P-256, and the curve `b`
93
+ constant `5ac635d8…27d2604b` is used during point decompression.
94
+
95
+ ```javascript
96
+ p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
97
+ n = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
98
+ a = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc // -3 mod p
99
+ b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
100
+ Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
101
+ Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
102
+ ```
103
+
104
+ ### The static point `S`
105
+
106
+ A 33-byte SEC1-compressed P-256 point stored verbatim in the Hardware
107
+ Manager binary at file offset `0x7b0490` (vmaddr `0x1007b0490`). The
108
+ prefix byte `0x03` indicates odd-Y:
109
+
110
+ ```
111
+ 03 28 08 12 90 39 6d 06 3e 51 14 52 6e d9 78 b9
112
+ 26 55 8f 6b a1 c9 6a d2 fa cf 76 ff fb 3f fe a0 fe
113
+ ```
114
+
115
+ Embedded as `STATIC_POINT_COMPRESSED` in
116
+ [`tools/derive-dmx-key.mjs`](tools/derive-dmx-key.mjs#L68).
117
+
118
+ (An earlier RE pass tried XOR-masking these bytes with `0x51, 0x52, …`,
119
+ which produces a different point used elsewhere in the app. That isn't the
120
+ KDF static point — the raw bytes above are the ones that reproduce a real
121
+ session's installed AES key. Confirmed by `tools/verify-kdf.mjs` against an
122
+ lldb-dumped ephemeral `d` plus the Stick pubkey from a captured handshake.)
123
+
124
+ ### Endianness
125
+
126
+ The Stick stores every 256-bit coordinate as **little-endian limbs** (byte 0
127
+ is the least-significant byte). Node's `crypto` works in big-endian SEC1
128
+ form, so a byte-reverse happens at every wire boundary:
129
+
130
+ - The 64-byte public key sent on opcode 0x0F is `LE(X) ‖ LE(Y)`. See
131
+ `pointToWire()` / `wireToPoint()` in `derive-dmx-key.mjs`.
132
+ - The installed AES key is the **little-endian form** of `X(d·S) ⊕ X(d·Q)`.
133
+ See `deriveDmxKey()` lines 128-135.
134
+
135
+ Forgetting the byte-reverse on the key was a multi-day debugging detour.
136
+
137
+ ### Plaintext header `P0`
138
+
139
+ The first 16 bytes of every frame's plaintext are a fixed constant baked
140
+ into Hardware Manager:
141
+
142
+ ```
143
+ 5b 4e 99 da 96 85 ad 97 6c 43 2b 0a 7f f9 ff cc
144
+ ```
145
+
146
+ Embedded as `P0` in [`tools/send_dmx.mjs`](tools/send_dmx.mjs#L41). The
147
+ Stick decrypts the body and verifies the first 16 bytes equal `P0` before
148
+ accepting the frame. This is effectively a known-plaintext anchor /
149
+ integrity check — a frame encrypted with the wrong key fails this check
150
+ even though CBC otherwise has no built-in authentication.
151
+
152
+ The 16 bytes immediately after `P0` are zero in every observed frame; we
153
+ send zeros there. The 512 DMX channel bytes follow at plaintext offset 32.
154
+
155
+ ## 3. The 0x011c reply
156
+
157
+ The TCP/2431 reply to opcode 0x011c is encrypted device-info using the same
158
+ AES-256 session key. The body is not needed for the DMX control path and
159
+ its plaintext schema is not fully documented here. Tooling that decrypted
160
+ it for analysis:
161
+
162
+ - [`tools/extract-011c.mjs`](tools/extract-011c.mjs)
163
+ - [`tools/find-011c-key.mjs`](tools/find-011c-key.mjs)
164
+ - [`tools/decrypt-011c.mjs`](tools/decrypt-011c.mjs)
165
+ - [`tools/verify-011c-key.mjs`](tools/verify-011c-key.mjs)
166
+ - [`tools/diff-011c.mjs`](tools/diff-011c.mjs)
167
+
168
+ `send_dmx.mjs` sends the 0x011c request (it's part of the wire-faithful
169
+ handshake sequence) and discards the reply.
170
+
171
+ ## What's NOT encrypted
172
+
173
+ - **Discovery broadcasts** on UDP/2430 (the four `LSAG_ALL`/`Stick_U1`/
174
+ `Stick_3A`/`Siudi_7B` startup packets) are clear.
175
+ - **UDP/2430 "Quick Trigger"** is clear and unauthenticated by design — see
176
+ [PROTOCOL.md §Quick Trigger](PROTOCOL.md#udp2430--quick-trigger-documented-but-limited).
177
+ Scene-level only; cannot set individual DMX channels.
178
+ - **`LIGHTINGSOFT_XHL`** beacons on UDP/24299 are clear.
179
+ - **The 32-byte UDP/2431 DMX frame header** is clear (this is what carries
180
+ the IV components).
181
+ - **TCP opcodes 0x47, 0x48, and the handshake chatter (0x46, 0x09, 0x00,
182
+ 0x05, 0x10, 0x0F, 0x70, 0x71, 0x74, 0x75, 0x2e, 0x11)** are clear. Only
183
+ the 0x011c reply body and the DMX UDP body are AES-encrypted.
184
+
185
+ ## Verification harness
186
+
187
+ The KDF self-test runs without hardware:
188
+
189
+ ```sh
190
+ node tools/derive-dmx-key.mjs
191
+ ```
192
+
193
+ It exercises ECDH symmetry, the wire round-trip, and the real static point.
194
+ End-to-end verification against captured sessions uses
195
+ `tools/verify-kdf.mjs`, `tools/verify-011c-key.mjs`, and the lldb dumps in
196
+ `tools/lldb_*.py`.
197
+
198
+ ## Provenance summary
199
+
200
+ | Constant | Where it lives in HWM | Recovery method |
201
+ |----------|----------------------|-----------------|
202
+ | `AUTH_KEY` (15B) | string literal | `tools/hmac-key.sh`, lldb HMAC trace |
203
+ | `STATIC_POINT_COMPRESSED` (33B) | file offset `0x7b0490` | static RE (Ghidra), verified against captured KDF output |
204
+ | `P0` (16B plaintext header) | constant inlined in frame builder | static RE, verified against decrypted captured frame |
205
+ | Curve = P-256 | code constants | byte-match against NIST P-256 |
206
+ | Mode = AES-256-CBC | custom-inlined AES, S-box at `DAT_1007c0010` | static RE + ciphertext byte-match |
207
+ | KDF = `X(d·S) ⊕ X(d·Q)` | `FUN_100107650` | static RE, verified `tools/verify-kdf.mjs` |
package/PROTOCOL.md ADDED
@@ -0,0 +1,277 @@
1
+ # Stick-DE3 Protocol
2
+
3
+ What we know about the Nicolaudie Stick-DE3 wire protocol, focused on the path
4
+ needed to drive per-channel DMX from a third-party client. Everything below
5
+ was reverse-engineered from packet captures of Hardware Manager / ESA Pro 2
6
+ plus static RE of the 2024-03-21 macOS Hardware Manager binary.
7
+
8
+ The authoritative implementation is [`tools/send_dmx.mjs`](tools/send_dmx.mjs).
9
+ This document is the explanation; the code is the spec.
10
+
11
+ For the encryption details (KDF, key material, AES mode), see
12
+ [ENCRYPTION.md](ENCRYPTION.md).
13
+
14
+ ## Ports & transports
15
+
16
+ | Port | Transport | Direction | Purpose |
17
+ |------|-----------|-----------|---------|
18
+ | 2431 | TCP | client → Stick | Control + per-session key exchange. Single session, mutually exclusive with HWM. |
19
+ | 2431 | UDP | client → Stick | Live DMX stream (encrypted 576-byte frames). |
20
+ | 2430 | UDP | bidirectional, broadcast | Discovery + the documented "Quick Trigger" scene control. |
21
+ | 24299 | UDP | client broadcast | Hardware Manager presence announce (`LIGHTINGSOFT_XHL`). |
22
+
23
+ The DMX UDP stream uses **source port 2430** and destination port 2431.
24
+ This pairing matters — the Stick filters on it.
25
+
26
+ ## Message framing (TCP/2431)
27
+
28
+ Every TCP message starts with an 8-byte ASCII magic, then a 2-byte
29
+ little-endian opcode, then opcode-specific payload:
30
+
31
+ ```
32
+ +--------+--------+----------------+
33
+ | magic | opcode | payload … |
34
+ | 8B | 2B LE | variable |
35
+ +--------+--------+----------------+
36
+ ```
37
+
38
+ Two magics are used:
39
+
40
+ - `Stick_3A` — the Stick's identity; used for most messages.
41
+ - `LSAG_ALL` — broadcast/handshake magic; used for the initial auth opcodes
42
+ (0x47, 0x48) and for discovery.
43
+
44
+ Most payloads begin with an 8-byte session token. The token is a single
45
+ monotonically-increasing little-endian uint32 (padded to 8 bytes) that
46
+ **continues across both TCP messages and the UDP DMX frames** within the same
47
+ session. Restarting the counter, or using a separate counter for UDP, causes
48
+ the Stick to drop every frame. Initial value in our implementation is `0x80`
49
+ to mirror HWM.
50
+
51
+ ## TCP handshake (go-live)
52
+
53
+ Sequence observed from HWM and replicated by `send_dmx.mjs`. Numbers in
54
+ parens are the opcode in hex.
55
+
56
+ 1. **`LSAG_ALL` 0x47 — hello.** Client sends `magic ‖ opcode ‖ token(8)`.
57
+ Stick replies with a 54-byte message; bytes `[0x16:0x36]` are a 32-byte
58
+ per-session "Stick handshake key" used in step 2.
59
+ 2. **`LSAG_ALL` 0x48 — authenticated handshake.** Layout:
60
+ ```
61
+ magic(8) ‖ 0x48 ‖ token(8) ‖ softwareName(32) ‖ stickHandshakeKey(32) ‖ hmac(32)
62
+ ```
63
+ The HMAC is `HMAC-SHA256(AUTH_KEY, first 82 bytes)`. `AUTH_KEY` is a
64
+ 15-byte string baked into Hardware Manager (see [ENCRYPTION.md](ENCRYPTION.md)
65
+ for the recovered value). Reply contains a status uint32 at `[0x12]`:
66
+ `0` = ok, `100` = `PermissionDenied`. A rejected HMAC means the session
67
+ never gets promoted to live.
68
+ 3. **Pre-DMX chatter** — six small messages, **each sent in its own TCP
69
+ segment** (HWM never coalesces these; coalescing them empirically prevents
70
+ the Stick from emitting its 0xc9 status):
71
+ ```
72
+ Stick_3A 0x46 4 zero bytes
73
+ Stick_3A 0x09 "14000000"
74
+ Stick_3A 0x09 "14000000"
75
+ Stick_3A 0x00 "14000000"
76
+ Stick_3A 0x011c token(8) ‖ "01001600"
77
+ Stick_3A 0x05 "0200"
78
+ ```
79
+ After this the Stick sends a `0x00c9` status indicating the session is
80
+ registered. Our implementation paces these with `CHATTER_MS` (default 10ms;
81
+ tested down to 5ms with no failures).
82
+ 4. **`Stick_3A` 0x10 — crypto-state query.** Reply has a state uint32 at
83
+ `[0x12]`: state 3 on a fresh device, state 4 if a DMX key from a previous
84
+ session is still latched.
85
+ 5. **`Stick_3A` 0x0F — DMX key exchange.** Client sends its 64-byte P-256
86
+ ephemeral public key (X ‖ Y, both little-endian limbs — see
87
+ [ENCRYPTION.md](ENCRYPTION.md)). Stick replies with its own 64-byte
88
+ P-256 public key at `[0x16:0x56]`. The Stick's public key has been
89
+ observed identical across every session captured to date; a static
90
+ fallback is hardcoded in `send_dmx.mjs` for resiliency.
91
+ 6. **Device sync chatter** — observed from HWM, byte-for-byte:
92
+ ```
93
+ Stick_3A 0x10 token
94
+ Stick_3A 0x75 token
95
+ Stick_3A 0x74 token
96
+ Stick_3A 0x71 token ‖ "0200000000"
97
+ Stick_3A 0x71 token ‖ "0100000000"
98
+ Stick_3A 0x71 token ‖ "0100000000"
99
+ Stick_3A 0x71 token ‖ "02b37f0000"
100
+ ```
101
+ These appear to be informational reads. The 4th `0x71` was the one HWM
102
+ pattern previously missed by our impl.
103
+ 7. **`Stick_3A` 0x70 — sector reads (skippable).** HWM reads sector 0
104
+ followed by sectors 63..185 (124 reads) to populate its commissioning UI.
105
+ Empirically the Stick will latch values even with `SECTORS=0`; this is
106
+ HWM UI chatter, not a Stick precondition. `send_dmx.mjs` defaults to
107
+ skipping it (saves ~1.5s/transaction).
108
+ 8. **`Stick_3A` 0x2e — go-live primer.** 32 zero bytes payload. HWM then
109
+ waits ~3.7s (UI-paced); the Stick only needs a much shorter settle. We
110
+ use 50ms via `SETTLE_2E_MS`.
111
+ 9. **`Stick_3A` 0x10, 0x11, 0x10** — enters live mode. The 0x11 reply
112
+ confirms live mode is active; a follow-up 0x10 should now report crypto
113
+ state 4.
114
+
115
+ ## DMX UDP frames (576 bytes)
116
+
117
+ Once the session is live, DMX values are sent as encrypted 576-byte UDP
118
+ datagrams from src-port 2430 → dst-port 2431.
119
+
120
+ ```
121
+ +----------------------------------+-----------------------------------------+
122
+ | 32-byte clear header | 544-byte AES-256-CBC ciphertext |
123
+ +----------------------------------+-----------------------------------------+
124
+ ```
125
+
126
+ ### Clear header (32 bytes)
127
+
128
+ | Offset | Size | Field | Value / meaning |
129
+ |--------|------|-------|-----------------|
130
+ | 0x00 | 8 | magic | `Stick_3A` |
131
+ | 0x08 | 2 | opcode | `0x0019` LE |
132
+ | 0x0a | 8 | `fieldA` | session token (same counter as TCP tokens — continues the sequence) |
133
+ | 0x12 | 2 | port / universe | DMX port selector, LE |
134
+ | 0x14 | 2 | channel count | `512` LE |
135
+ | 0x16 | 1 | constant | `100` (HWM's value) |
136
+ | 0x17 | 1 | sequence byte | per-frame counter mod 256 |
137
+ | 0x18 | 8 | nonce | random per frame |
138
+
139
+ ### AES IV
140
+
141
+ The 16-byte AES-CBC IV is `fieldA (8) ‖ nonce (8)` — i.e. the two header
142
+ fields at offsets 0x0a and 0x18. The receiver pulls these straight out of
143
+ the clear header to decrypt.
144
+
145
+ ### Plaintext layout (544 bytes)
146
+
147
+ ```
148
+ +---------------------+---------------------+-------------------------+
149
+ | P0 (16B constant) | header2 (16B zeros) | 512 DMX channel bytes |
150
+ +---------------------+---------------------+-------------------------+
151
+ 0 16 32 544
152
+ ```
153
+
154
+ `P0` is a fixed 16-byte constant baked into HWM (see
155
+ [ENCRYPTION.md](ENCRYPTION.md)). It serves as a known-plaintext anchor —
156
+ when the Stick decrypts a frame, the first 16 bytes of plaintext must
157
+ equal `P0` or the frame is dropped.
158
+
159
+ `header2` has been observed all-zero in every captured frame. It may be
160
+ meaningful in some other path; we send zeros and the Stick accepts it.
161
+
162
+ **Channel data starts at plaintext offset 32**, not 16. This was an
163
+ off-by-16 bug in an earlier impl (we lit HWM's channel 6 when sending to
164
+ ours-channel 22, because 22 - 16 = 6).
165
+
166
+ ## Transactional streaming & the latch
167
+
168
+ The Stick holds the last received DMX values **on disconnect** (any kind that
169
+ doesn't send HWM's "polite goodbye" opcode — which we have never observed
170
+ identified). This lets a client be transactional rather than maintaining a
171
+ persistent stream:
172
+
173
+ 1. Connect, run the handshake, derive the session key.
174
+ 2. Stream encrypted DMX frames at 25Hz for ~750ms (~19 frames). The Stick
175
+ commits values after roughly 12 frames at 25Hz; below ~500ms wall-time
176
+ the commit doesn't happen.
177
+ 3. `socket.destroy()` (RST). The Stick latches the last frame's values.
178
+
179
+ Trade-off: each transaction takes ~1.5–2s end-to-end (handshake + stream +
180
+ disconnect), and the wall controller blinks briefly on disconnect — HWM
181
+ exhibits the same blink, so it's at the protocol layer, not avoidable from
182
+ the client side.
183
+
184
+ ## UDP/2430 broadcast — discovery
185
+
186
+ HWM emits a startup burst on UDP/2430 (255.255.255.255):
187
+
188
+ ```
189
+ LSAG_ALL <8B zeros> 14 00 00 00
190
+ Stick_U1 <8B zeros> 14 00 00 00
191
+ Stick_3A <8B zeros> 14 00 00 00
192
+ Siudi_7B <8B zeros> 14 00 00 00
193
+ ```
194
+
195
+ …repeated three times with a 25ms gap. `send_dmx.mjs` does the same before
196
+ connecting; it doesn't appear to be required (the Stick is reachable by IP
197
+ without it) but matches HWM exactly.
198
+
199
+ ## UDP/2430 — "Quick Trigger" (documented but limited)
200
+
201
+ Nicolaudie's STICK Remote Protocol spec defines a 24-byte UDP packet on port
202
+ 2430 with **no authentication** and **no encryption**. See
203
+ [`tools/quick-trigger.mjs`](tools/quick-trigger.mjs):
204
+
205
+ ```
206
+ [0..7] "Stick_3A"
207
+ [8..9] opcode 109 (0x6D 0x00)
208
+ [10..11] Scene number (page*50 + sceneInPage), LE
209
+ [12] ZoneSyncId
210
+ [13] Command (0=scene-off, 1=scene-on, 5=dimmer, 7=color, 8/9=blackout, …)
211
+ [14..15] Dimmer, LE
212
+ [16..17] Speed, LE
213
+ [18..19] unused
214
+ [20..23] R, G, B, 0
215
+ ```
216
+
217
+ Useful for testing connectivity but **scene-level only**: it cannot set
218
+ individual DMX channels, and on fw3.08 it is gated by "Security for Cloud
219
+ Access" (must be disabled in Hardware Manager for the device to action it).
220
+ For per-fixture HomeKit control, the encrypted UDP/2431 path is the only
221
+ route.
222
+
223
+ ## UDP/24299 — `LIGHTINGSOFT_XHL` presence broadcast
224
+
225
+ HWM broadcasts an "I am Hardware Manager" beacon on UDP/24299 (src+dst
226
+ 24299) at startup: one 114-byte announce containing the literal string
227
+ `Hardware Manager`, followed by two 46-byte status packets. The 32-byte
228
+ header is:
229
+
230
+ ```
231
+ "LIGHTINGSOFT_XHL" 16B
232
+ 00 00 00 00 00 00 00 00 8B zeros
233
+ 14 00 00 00 op/len = 20
234
+ 01 00 00 00 version = 1
235
+ ```
236
+
237
+ We have no Stick-side traffic acknowledging these but HWM emits them
238
+ consistently, and `send_dmx.mjs` replicates them. Mode of action unconfirmed.
239
+
240
+ ## TCP/2431 — passive status
241
+
242
+ Per the STICK Remote Protocol spec, once a client connects to TCP/2431 the
243
+ Stick emits a status packet every 5s. [`tools/stick-status.mjs`](tools/stick-status.mjs)
244
+ reads it:
245
+
246
+ | Offset | Field |
247
+ |--------|-------|
248
+ | 0..7 | `Stick_3A` |
249
+ | 8 | opcode |
250
+ | 9 | version |
251
+ | 10..11 | scene number |
252
+ | 12..23 | scene name (NUL-terminated) |
253
+ | 24 | zone number |
254
+ | 25..36 | zone name |
255
+ | 37..38 | dimmer |
256
+ | 39..41 | R, G, B |
257
+ | 42..43 | speed |
258
+ | 47 | **RemoteClientsCount** |
259
+ | 48 | **LiveModeIsActivated** (1 = live/remote, 0 = standalone) |
260
+
261
+ On fw2.x/3.x the LSAG/auth challenge must complete before status is emitted;
262
+ without that the client sees only the auth-challenge reply.
263
+
264
+ ## Open questions
265
+
266
+ - **The "polite goodbye" opcode** — HWM has a right-click menu option that
267
+ cleanly disconnects and *does not* latch. The opcode that triggers that
268
+ exit path has not been identified. Every other disconnect we've tried
269
+ (FIN, RST, process kill, cmd-Q) latches.
270
+ - **The 0x011c reply body** is AES-encrypted device-info; the full payload
271
+ schema is undocumented. `tools/decrypt-011c.mjs`, `extract-011c.mjs`,
272
+ `find-011c-key.mjs`, `verify-011c-key.mjs`, `diff-011c.mjs` were used to
273
+ recover its key; the contents are not needed for the DMX path.
274
+ - **The 0x10 crypto-state values** — 3 = pre-handshake, 4 = key installed.
275
+ Other values not observed.
276
+ - **`Siudi_7B`** discovery magic — the Stick-DE3 doesn't respond to this
277
+ one; presumably it targets a different product line.
package/README.md CHANGED
@@ -8,6 +8,28 @@ stream, reverse-engineered from the ESA2 Hardware Manager).
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/@ecopoesis/homebridge-dmx.svg)](https://www.npmjs.com/package/@ecopoesis/homebridge-dmx)
10
10
 
11
+ ## Nicolaudie Stick-DE3
12
+
13
+ Please, under no circumstances, should you buy a Nicolaudie Stick-DE3 because this plugin exists.
14
+
15
+ The Stick-DE3 hardware, and its accompanying software, are some of the worst I have had the misfortune to use.
16
+
17
+ Some of the litany of problems with the Stick-DE3 ecosystem:
18
+ * The Stick does not support power-over-ethernet. It takes power over an Ethernet (8P8C) shaped port, but it's non-standard.
19
+ * The Stick touchscreen is very touchy. And not in a good way. Phantom clicks, slow responsiveness: all the problems from pre-iPhone touchscreens are here. I believe I've clicked buttons by looking at it too hard.
20
+ * Every setting change also changes the MAC address. Hope you didn't want to give it a static address via DHCP.
21
+ * HSV-style color spaces are not supported at all. Except: look at the color-wheel. It's in HSV. So they do support converting from HSV to RGB or various RGB+W derivatives, but can't pass the native values their controller's UI generates straight through to DMX channels.
22
+ * The software suite is ancient and terrible. ESA Pro, ESA Pro 2, and Hardware Manager all only support x86 on MacOS. In 2026. Apple Silicon came out 6 years ago!
23
+ * The software has a UI that would have looked dated in Windows 95.
24
+ * Why is there so much encryption? This is my hardware. Nicolaudie sensibly ships a way to turn off IoT access, making this a pure NoT ("Network of Things") device. Let me talk to it.
25
+ * Why do you make a DMX interface without simple channel control? It'd be great if I could use this as intended, but since it doesn't support HSV-style color, give me an easy escape hatch. You have a (poorly) documented API. Just add the ability for me to send a DMX packet. I shouldn't need to break AES to do that.
26
+
27
+ That said, this plugin does just that. Reusing the encryption scheme used by Hardware Manager (with keys extracted from the binary) lets us send the same type of per-channel control used there. We then cleanly disconnect; the Stick latches the last frame, with a brief wall-controller blink as a side effect.
28
+
29
+ `send_dmx.mjs` is the pieces of the protocol needed to authenticate and start streaming, including the keys extracted from Hardware Manager. Other files in `tools` are the code developed to get to that point.
30
+
31
+ If you, like me, bought a Stick-DE3 based on the assumption that it was competently executed and are now stuck with it, hopefully this repo and the research it contains can help you actually make it useful.
32
+
11
33
  ## Status
12
34
 
13
35
  | Piece | State |
@@ -48,6 +48,14 @@
48
48
  "format": "ipv4",
49
49
  "required": true,
50
50
  "description": "Static IP of the controller on its DMX VLAN (e.g. 192.168.96.2). Discovery is not supported."
51
+ },
52
+ "refreshMinutes": {
53
+ "title": "Keepalive refresh (minutes)",
54
+ "type": "integer",
55
+ "required": false,
56
+ "default": 30,
57
+ "minimum": 0,
58
+ "description": "Re-send the last-known DMX state after this many idle minutes. Keeps the Stick-DE3 from drifting into a wedged no-output state and re-asserts state after a power-cycle. 0 disables."
51
59
  }
52
60
  }
53
61
  }
package/dist/config.d.ts CHANGED
@@ -3,6 +3,7 @@ export interface ControllerSpec {
3
3
  id: string;
4
4
  type: string;
5
5
  ip: string;
6
+ refreshMinutes?: number;
6
7
  }
7
8
  export interface ProfileSpec {
8
9
  name: string;
@@ -41,6 +42,7 @@ export interface Controller {
41
42
  id: string;
42
43
  type: ControllerType;
43
44
  ip: string;
45
+ refreshMinutes: number;
44
46
  }
45
47
  export interface Fixture {
46
48
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAuCA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI5E,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,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;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;IACpB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB;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;;wEAEwE;AACxE,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,eAAe,EAAE,gBAAgB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;;;oEAIoE;AACpE,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CAmLzE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAuCA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAK5E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;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,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;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;IACpB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB;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;IACX,cAAc,EAAE,MAAM,CAAC;CACxB;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;;wEAEwE;AACxE,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,eAAe,EAAE,gBAAgB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;;;oEAIoE;AACpE,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CAyLzE"}
package/dist/config.js CHANGED
@@ -35,6 +35,7 @@
35
35
  import fs from 'node:fs';
36
36
  import path from 'node:path';
37
37
  import yaml from 'js-yaml';
38
+ import { REFRESH_MINUTES_DEFAULT } from './settings.js';
38
39
  import { getColorModel } from './color/index.js';
39
40
  import { parseChannelName } from './color/parsers.js';
40
41
  export const CONTROLLER_TYPES = ['StickDE3'];
@@ -75,7 +76,13 @@ export function loadConfig(rawJson, cwd) {
75
76
  if (!cs.ip || typeof cs.ip !== 'string') {
76
77
  throw new Error(`controller "${cs.id}": required field "ip" missing`);
77
78
  }
78
- controllersById.set(cs.id, { id: cs.id, type: cs.type, ip: cs.ip });
79
+ const refreshMinutes = cs.refreshMinutes ?? REFRESH_MINUTES_DEFAULT;
80
+ if (typeof refreshMinutes !== 'number' || refreshMinutes < 0) {
81
+ throw new Error(`controller "${cs.id}": refreshMinutes must be a number >= 0 (0 disables)`);
82
+ }
83
+ controllersById.set(cs.id, {
84
+ id: cs.id, type: cs.type, ip: cs.ip, refreshMinutes,
85
+ });
79
86
  }
80
87
  // Profiles
81
88
  const profileSpecs = raw.profiles ?? [];
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -5,28 +5,39 @@ export declare class StickController {
5
5
  private readonly ip;
6
6
  private readonly log;
7
7
  private readonly debounceMs;
8
+ private readonly refreshMs;
8
9
  private targets;
9
10
  private lastChangeAt;
11
+ private lastTxEndAt;
10
12
  private streaming;
13
+ private refreshTimer?;
11
14
  /** Tracks whether the very first subprocess in this controller's
12
15
  * lifetime should set RUN_PROBE=1. After a Stick power-cycle (which
13
16
  * we can't detect in-band) the probe is required to (re-)register
14
17
  * the client. Subsequent subprocesses can skip it. */
15
18
  private needsProbe;
16
- constructor(ip: string, log: Logger, debounceMs?: number);
19
+ constructor(ip: string, log: Logger, debounceMs?: number, refreshMs?: number);
20
+ /** Idle keepalive: the Stick wedges (sessions succeed, output dark) after
21
+ * hours without traffic. Re-send the current universe state whenever the
22
+ * controller has been idle for refreshMs. Also re-asserts state after an
23
+ * out-of-band power-cycle of the Stick. */
24
+ private maybeRefresh;
17
25
  /** Stage a fixture's bytes into the universe target buffer and ensure
18
26
  * the streaming loop is running. */
19
27
  setFixture(fixture: Fixture, state: HomeKitLightState): void;
20
28
  private universeBuf;
21
29
  /** Build the CLI args from current targets. Only non-zero channels are
22
- * passed — send_dmx initialises the universe to 0 so omission == 0. */
30
+ * passed — send_dmx initialises the universe to 0 so omission == 0.
31
+ * When everything is 0 (all lights off), send an explicit zero so
32
+ * send_dmx still has at least one assignment to transact. */
23
33
  private buildArgs;
24
34
  /** Subprocess approach: spawn tools/send_dmx.mjs for each transaction.
25
35
  * Fresh node process = no in-process state poisoning that breaks
26
36
  * session 2+ in the long-lived plugin. Costs ~1.5-3s per spawn (process
27
37
  * startup + handshake + 750ms stream + RST) but is empirically reliable. */
28
38
  private run;
29
- /** Shutdown hook. Subprocess approach has no long-lived state. */
39
+ /** Shutdown hook. Stop the keepalive timer; the subprocess approach has
40
+ * no other long-lived state. */
30
41
  shutdown(): void;
31
42
  }
32
43
  //# sourceMappingURL=controller.d.ts.map
@@ -1 +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"}
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;IAaxB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAf5B,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC;;;2DAGuD;IACvD,OAAO,CAAC,UAAU,CAAQ;gBAGP,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAoB,EAChC,SAAS,GAAE,MAAU;IAQxC;;;gDAG4C;IAC5C,OAAO,CAAC,YAAY;IAcpB;yCACqC;IACrC,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAmB5D,OAAO,CAAC,WAAW;IAMnB;;;kEAG8D;IAC9D,OAAO,CAAC,SAAS;IAcjB;;;iFAG6E;YAC/D,GAAG;IAgDjB;qCACiC;IACjC,QAAQ,IAAI,IAAI;CAGjB"}
@@ -10,7 +10,7 @@
10
10
  import { spawn } from 'node:child_process';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import path from 'node:path';
13
- import { DEBOUNCE_MS } from './settings.js';
13
+ import { DEBOUNCE_MS, REFRESH_CHECK_MS } from './settings.js';
14
14
  import { sleep } from './stick/protocol.js';
15
15
  const __filename = fileURLToPath(import.meta.url);
16
16
  const __dirname = path.dirname(__filename);
@@ -20,18 +20,45 @@ export class StickController {
20
20
  ip;
21
21
  log;
22
22
  debounceMs;
23
+ refreshMs;
23
24
  targets = new Map();
24
25
  lastChangeAt = 0;
26
+ lastTxEndAt = 0;
25
27
  streaming = false;
28
+ refreshTimer;
26
29
  /** Tracks whether the very first subprocess in this controller's
27
30
  * lifetime should set RUN_PROBE=1. After a Stick power-cycle (which
28
31
  * we can't detect in-band) the probe is required to (re-)register
29
32
  * the client. Subsequent subprocesses can skip it. */
30
33
  needsProbe = true;
31
- constructor(ip, log, debounceMs = DEBOUNCE_MS) {
34
+ constructor(ip, log, debounceMs = DEBOUNCE_MS, refreshMs = 0) {
32
35
  this.ip = ip;
33
36
  this.log = log;
34
37
  this.debounceMs = debounceMs;
38
+ this.refreshMs = refreshMs;
39
+ if (refreshMs > 0) {
40
+ this.refreshTimer = setInterval(() => this.maybeRefresh(), REFRESH_CHECK_MS);
41
+ this.refreshTimer.unref?.();
42
+ }
43
+ }
44
+ /** Idle keepalive: the Stick wedges (sessions succeed, output dark) after
45
+ * hours without traffic. Re-send the current universe state whenever the
46
+ * controller has been idle for refreshMs. Also re-asserts state after an
47
+ * out-of-band power-cycle of the Stick. */
48
+ maybeRefresh() {
49
+ if (this.streaming || this.targets.size === 0)
50
+ return;
51
+ const idleMs = Date.now() - Math.max(this.lastTxEndAt, this.lastChangeAt);
52
+ if (idleMs < this.refreshMs)
53
+ return;
54
+ this.log.info(`keepalive refresh after ${Math.round(idleMs / 60_000)}m idle`);
55
+ this.needsProbe = true; // the Stick may have power-cycled while we were idle
56
+ this.streaming = true;
57
+ this.run().catch((e) => {
58
+ this.log.error('keepalive refresh crashed:', e.message);
59
+ }).finally(() => {
60
+ this.streaming = false;
61
+ });
35
62
  }
36
63
  /** Stage a fixture's bytes into the universe target buffer and ensure
37
64
  * the streaming loop is running. */
@@ -62,7 +89,9 @@ export class StickController {
62
89
  return arr;
63
90
  }
64
91
  /** Build the CLI args from current targets. Only non-zero channels are
65
- * passed — send_dmx initialises the universe to 0 so omission == 0. */
92
+ * passed — send_dmx initialises the universe to 0 so omission == 0.
93
+ * When everything is 0 (all lights off), send an explicit zero so
94
+ * send_dmx still has at least one assignment to transact. */
66
95
  buildArgs() {
67
96
  const args = [SEND_DMX_PATH, this.ip];
68
97
  for (const [u, arr] of this.targets) {
@@ -71,6 +100,10 @@ export class StickController {
71
100
  args.push(`${u},${i + 1}=${arr[i]}`);
72
101
  }
73
102
  }
103
+ if (args.length === 2 && this.targets.size > 0) {
104
+ const firstU = this.targets.keys().next().value;
105
+ args.push(`${firstU},1=0`);
106
+ }
74
107
  return args;
75
108
  }
76
109
  /** Subprocess approach: spawn tools/send_dmx.mjs for each transaction.
@@ -107,6 +140,7 @@ export class StickController {
107
140
  });
108
141
  });
109
142
  const elapsedMs = Date.now() - startedAt;
143
+ this.lastTxEndAt = Date.now();
110
144
  if (exitCode === 0) {
111
145
  this.needsProbe = false;
112
146
  this.log.info(`send_dmx done in ${elapsedMs}ms`);
@@ -120,9 +154,11 @@ export class StickController {
120
154
  return;
121
155
  }
122
156
  }
123
- /** Shutdown hook. Subprocess approach has no long-lived state. */
157
+ /** Shutdown hook. Stop the keepalive timer; the subprocess approach has
158
+ * no other long-lived state. */
124
159
  shutdown() {
125
- /* no-op */
160
+ if (this.refreshTimer)
161
+ clearInterval(this.refreshTimer);
126
162
  }
127
163
  }
128
164
  //# sourceMappingURL=controller.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
package/dist/platform.js CHANGED
@@ -8,7 +8,7 @@ import { StickController } from './controller.js';
8
8
  import { StickFixture } from './platformAccessory.js';
9
9
  import { StateRegistry } from './stateRegistry.js';
10
10
  import { ZoneFixture } from './zoneAccessory.js';
11
- import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
11
+ import { DEBOUNCE_MS, PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
12
12
  export class DmxPlatform {
13
13
  log;
14
14
  config;
@@ -48,7 +48,7 @@ export class DmxPlatform {
48
48
  makeController(c) {
49
49
  switch (c.type) {
50
50
  case 'StickDE3':
51
- return new StickController(c.ip, this.log);
51
+ return new StickController(c.ip, this.log, DEBOUNCE_MS, c.refreshMinutes * 60_000);
52
52
  default:
53
53
  throw new Error(`unsupported controller type "${c.type}"`);
54
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,15 @@ export declare const CHARACTERISTIC_UPDATE_DELAY_MS = 50;
7
7
  * this long before spawning the send_dmx subprocess. Coalesces a fast
8
8
  * slider drag into one transaction. */
9
9
  export declare const DEBOUNCE_MS = 750;
10
+ /** Idle keepalive. The Stick-DE3 drifts into a wedged state after hours
11
+ * without traffic: sessions and the crypto handshake still succeed but
12
+ * live DMX output goes dark (observed 2026-08-22..25; only a hard power
13
+ * cycle recovers it). Re-sending the last-known state whenever the
14
+ * controller has been idle this long keeps the device exercised and
15
+ * re-asserts state after an out-of-band power-cycle. 0 disables. */
16
+ export declare const REFRESH_MINUTES_DEFAULT = 30;
17
+ /** How often the controller checks whether a keepalive refresh is due. */
18
+ export declare const REFRESH_CHECK_MS = 60000;
10
19
  /** Frame interval (40ms = 25Hz, matches HWM exactly). Unused by the
11
20
  * subprocess controller — send_dmx.mjs's own FRAME_HZ env knob is what
12
21
  * matters now. */
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,QAAQ,CAAC;AACnC,eAAO,MAAM,WAAW,8BAA8B,CAAC;AAEvD;6EAC6E;AAC7E,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD;;wCAEwC;AACxC,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B;;mBAEmB;AACnB,eAAO,MAAM,iBAAiB,KAAK,CAAC"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,QAAQ,CAAC;AACnC,eAAO,MAAM,WAAW,8BAA8B,CAAC;AAEvD;6EAC6E;AAC7E,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD;;wCAEwC;AACxC,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B;;;;;qEAKqE;AACrE,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC;;mBAEmB;AACnB,eAAO,MAAM,iBAAiB,KAAK,CAAC"}
package/dist/settings.js CHANGED
@@ -8,6 +8,15 @@ export const CHARACTERISTIC_UPDATE_DELAY_MS = 50;
8
8
  * this long before spawning the send_dmx subprocess. Coalesces a fast
9
9
  * slider drag into one transaction. */
10
10
  export const DEBOUNCE_MS = 750;
11
+ /** Idle keepalive. The Stick-DE3 drifts into a wedged state after hours
12
+ * without traffic: sessions and the crypto handshake still succeed but
13
+ * live DMX output goes dark (observed 2026-08-22..25; only a hard power
14
+ * cycle recovers it). Re-sending the last-known state whenever the
15
+ * controller has been idle this long keeps the device exercised and
16
+ * re-asserts state after an out-of-band power-cycle. 0 disables. */
17
+ export const REFRESH_MINUTES_DEFAULT = 30;
18
+ /** How often the controller checks whether a keepalive refresh is due. */
19
+ export const REFRESH_CHECK_MS = 60_000;
11
20
  /** Frame interval (40ms = 25Hz, matches HWM exactly). Unused by the
12
21
  * subprocess controller — send_dmx.mjs's own FRAME_HZ env knob is what
13
22
  * matters now. */
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAEvD;6EAC6E;AAC7E,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC;AAEjD;;wCAEwC;AACxC,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAE/B;;mBAEmB;AACnB,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAEvD;6EAC6E;AAC7E,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC;AAEjD;;wCAEwC;AACxC,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAE/B;;;;;qEAKqE;AACrE,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAE1C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEvC;;mBAEmB;AACnB,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ecopoesis/homebridge-dmx",
3
3
  "displayName": "DMX",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "description": "Homebridge platform for DMX controllers (currently Nicolaudie Stick-DE3)",
6
6
  "license": "Apache-2.0",
7
7
  "type": "module",
package/src/config.ts CHANGED
@@ -38,6 +38,7 @@ import path from 'node:path';
38
38
  import yaml from 'js-yaml';
39
39
 
40
40
  import { ChannelDef, ColorModel, HKCharacteristic } from './color/types.js';
41
+ import { REFRESH_MINUTES_DEFAULT } from './settings.js';
41
42
  import { getColorModel } from './color/index.js';
42
43
  import { parseChannelName } from './color/parsers.js';
43
44
 
@@ -45,6 +46,7 @@ export interface ControllerSpec {
45
46
  id: string; // unique handle, referenced from patch entries
46
47
  type: string; // 'StickDE3' (only one supported today)
47
48
  ip: string; // controller's static IP
49
+ refreshMinutes?: number; // idle keepalive: re-send state every N min (0 = off)
48
50
  }
49
51
 
50
52
  export interface ProfileSpec {
@@ -90,6 +92,7 @@ export interface Controller {
90
92
  id: string;
91
93
  type: ControllerType;
92
94
  ip: string;
95
+ refreshMinutes: number;
93
96
  }
94
97
 
95
98
  export interface Fixture {
@@ -159,7 +162,13 @@ export function loadConfig(rawJson: RawConfig, cwd?: string): LoadedConfig {
159
162
  if (!cs.ip || typeof cs.ip !== 'string') {
160
163
  throw new Error(`controller "${cs.id}": required field "ip" missing`);
161
164
  }
162
- controllersById.set(cs.id, { id: cs.id, type: cs.type as ControllerType, ip: cs.ip });
165
+ const refreshMinutes = cs.refreshMinutes ?? REFRESH_MINUTES_DEFAULT;
166
+ if (typeof refreshMinutes !== 'number' || refreshMinutes < 0) {
167
+ throw new Error(`controller "${cs.id}": refreshMinutes must be a number >= 0 (0 disables)`);
168
+ }
169
+ controllersById.set(cs.id, {
170
+ id: cs.id, type: cs.type as ControllerType, ip: cs.ip, refreshMinutes,
171
+ });
163
172
  }
164
173
 
165
174
  // Profiles
package/src/controller.ts CHANGED
@@ -15,7 +15,7 @@ import path from 'node:path';
15
15
 
16
16
  import { Fixture } from './config.js';
17
17
  import { HomeKitLightState } from './color/types.js';
18
- import { DEBOUNCE_MS } from './settings.js';
18
+ import { DEBOUNCE_MS, REFRESH_CHECK_MS } from './settings.js';
19
19
  import { sleep } from './stick/protocol.js';
20
20
 
21
21
  const __filename = fileURLToPath(import.meta.url);
@@ -26,7 +26,9 @@ const SEND_DMX_PATH = path.resolve(__dirname, '..', 'tools', 'send_dmx.mjs');
26
26
  export class StickController {
27
27
  private targets = new Map<number, Uint8Array>();
28
28
  private lastChangeAt = 0;
29
+ private lastTxEndAt = 0;
29
30
  private streaming = false;
31
+ private refreshTimer?: NodeJS.Timeout;
30
32
  /** Tracks whether the very first subprocess in this controller's
31
33
  * lifetime should set RUN_PROBE=1. After a Stick power-cycle (which
32
34
  * we can't detect in-band) the probe is required to (re-)register
@@ -37,7 +39,31 @@ export class StickController {
37
39
  private readonly ip: string,
38
40
  private readonly log: Logger,
39
41
  private readonly debounceMs: number = DEBOUNCE_MS,
40
- ) {}
42
+ private readonly refreshMs: number = 0,
43
+ ) {
44
+ if (refreshMs > 0) {
45
+ this.refreshTimer = setInterval(() => this.maybeRefresh(), REFRESH_CHECK_MS);
46
+ this.refreshTimer.unref?.();
47
+ }
48
+ }
49
+
50
+ /** Idle keepalive: the Stick wedges (sessions succeed, output dark) after
51
+ * hours without traffic. Re-send the current universe state whenever the
52
+ * controller has been idle for refreshMs. Also re-asserts state after an
53
+ * out-of-band power-cycle of the Stick. */
54
+ private maybeRefresh(): void {
55
+ if (this.streaming || this.targets.size === 0) return;
56
+ const idleMs = Date.now() - Math.max(this.lastTxEndAt, this.lastChangeAt);
57
+ if (idleMs < this.refreshMs) return;
58
+ this.log.info(`keepalive refresh after ${Math.round(idleMs / 60_000)}m idle`);
59
+ this.needsProbe = true; // the Stick may have power-cycled while we were idle
60
+ this.streaming = true;
61
+ this.run().catch((e) => {
62
+ this.log.error('keepalive refresh crashed:', (e as Error).message);
63
+ }).finally(() => {
64
+ this.streaming = false;
65
+ });
66
+ }
41
67
 
42
68
  /** Stage a fixture's bytes into the universe target buffer and ensure
43
69
  * the streaming loop is running. */
@@ -67,7 +93,9 @@ export class StickController {
67
93
  }
68
94
 
69
95
  /** Build the CLI args from current targets. Only non-zero channels are
70
- * passed — send_dmx initialises the universe to 0 so omission == 0. */
96
+ * passed — send_dmx initialises the universe to 0 so omission == 0.
97
+ * When everything is 0 (all lights off), send an explicit zero so
98
+ * send_dmx still has at least one assignment to transact. */
71
99
  private buildArgs(): string[] {
72
100
  const args: string[] = [SEND_DMX_PATH, this.ip];
73
101
  for (const [u, arr] of this.targets) {
@@ -75,6 +103,10 @@ export class StickController {
75
103
  if (arr[i] !== 0) args.push(`${u},${i + 1}=${arr[i]}`);
76
104
  }
77
105
  }
106
+ if (args.length === 2 && this.targets.size > 0) {
107
+ const firstU = this.targets.keys().next().value as number;
108
+ args.push(`${firstU},1=0`);
109
+ }
78
110
  return args;
79
111
  }
80
112
 
@@ -115,6 +147,7 @@ export class StickController {
115
147
  });
116
148
  });
117
149
  const elapsedMs = Date.now() - startedAt;
150
+ this.lastTxEndAt = Date.now();
118
151
 
119
152
  if (exitCode === 0) {
120
153
  this.needsProbe = false;
@@ -129,8 +162,9 @@ export class StickController {
129
162
  }
130
163
  }
131
164
 
132
- /** Shutdown hook. Subprocess approach has no long-lived state. */
165
+ /** Shutdown hook. Stop the keepalive timer; the subprocess approach has
166
+ * no other long-lived state. */
133
167
  shutdown(): void {
134
- /* no-op */
168
+ if (this.refreshTimer) clearInterval(this.refreshTimer);
135
169
  }
136
170
  }
package/src/platform.ts CHANGED
@@ -19,7 +19,7 @@ import { StickController } from './controller.js';
19
19
  import { StickFixture } from './platformAccessory.js';
20
20
  import { StateRegistry } from './stateRegistry.js';
21
21
  import { ZoneFixture } from './zoneAccessory.js';
22
- import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
22
+ import { DEBOUNCE_MS, PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
23
23
 
24
24
  export class DmxPlatform implements DynamicPlatformPlugin {
25
25
  public readonly Service: typeof Service;
@@ -68,7 +68,7 @@ export class DmxPlatform implements DynamicPlatformPlugin {
68
68
  private makeController(c: Controller): StickController {
69
69
  switch (c.type) {
70
70
  case 'StickDE3':
71
- return new StickController(c.ip, this.log);
71
+ return new StickController(c.ip, this.log, DEBOUNCE_MS, c.refreshMinutes * 60_000);
72
72
  default:
73
73
  throw new Error(`unsupported controller type "${(c as Controller).type}"`);
74
74
  }
package/src/settings.ts CHANGED
@@ -12,6 +12,17 @@ export const CHARACTERISTIC_UPDATE_DELAY_MS = 50;
12
12
  * slider drag into one transaction. */
13
13
  export const DEBOUNCE_MS = 750;
14
14
 
15
+ /** Idle keepalive. The Stick-DE3 drifts into a wedged state after hours
16
+ * without traffic: sessions and the crypto handshake still succeed but
17
+ * live DMX output goes dark (observed 2026-08-22..25; only a hard power
18
+ * cycle recovers it). Re-sending the last-known state whenever the
19
+ * controller has been idle this long keeps the device exercised and
20
+ * re-asserts state after an out-of-band power-cycle. 0 disables. */
21
+ export const REFRESH_MINUTES_DEFAULT = 30;
22
+
23
+ /** How often the controller checks whether a keepalive refresh is due. */
24
+ export const REFRESH_CHECK_MS = 60_000;
25
+
15
26
  /** Frame interval (40ms = 25Hz, matches HWM exactly). Unused by the
16
27
  * subprocess controller — send_dmx.mjs's own FRAME_HZ env knob is what
17
28
  * matters now. */
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ # stick-power-cycle — hard power-cycle the Nicolaudie Stick-DE3 by turning
3
+ # PoE off/on on its switch port (sw01 port 15, via PoE splitter).
4
+ #
5
+ # Why: the Stick can wedge into a state where TCP sessions and the crypto
6
+ # handshake succeed but live DMX output is blackout (seen 2026-08-22..24);
7
+ # only a real power removal recovers it. The UniFi UI "Power Cycle" bounce
8
+ # is too short — the splitter rides through it. This holds power off for
9
+ # OFF_SECONDS, then restores, then waits for the Stick to answer ping.
10
+ #
11
+ # Credentials: read at runtime from the homebridge config.json (same local
12
+ # UniFi user the AP-RGB plugin uses). Nothing secret is stored here.
13
+ #
14
+ # Scheduled via systemd user timer (host has no cron):
15
+ # ~/.config/systemd/user/stick-power-cycle.{service,timer} — daily 08:30 UTC
16
+ # (= 4:30am EDT; lights are always off then). Lingering enabled for miker so
17
+ # the timer fires without a login session. Logs: ~/stick-power-cycle.log
18
+ # Manual run: systemctl --user start stick-power-cycle.service
19
+
20
+ import json, socket, ssl, sys, time, urllib.request, http.cookiejar
21
+
22
+ CONTROLLER = 'https://192.168.1.1'
23
+ CONFIG_JSON = '/opt/containers/homebridge/config.json'
24
+ SWITCH_MAC = '94:2a:6f:94:f1:da' # sw01
25
+ PORT_IDX = 15 # Stick-DE3 PoE splitter, "dmx" VLAN 96
26
+ STICK_IP = '192.168.96.2'
27
+ OFF_SECONDS = 30
28
+ BOOT_WAIT_S = 180
29
+
30
+ def log(*a):
31
+ print(time.strftime('%Y-%m-%d %H:%M:%S%z'), *a, flush=True)
32
+
33
+ def load_creds():
34
+ cfg = json.load(open(CONFIG_JSON))
35
+ p = next(x for x in cfg['platforms'] if x.get('platform') == 'UnifiAPLight')
36
+ return p['username'], p['password']
37
+
38
+ class Unifi:
39
+ def __init__(self):
40
+ ctx = ssl.create_default_context()
41
+ ctx.check_hostname = False
42
+ ctx.verify_mode = ssl.CERT_NONE
43
+ self.op = urllib.request.build_opener(
44
+ urllib.request.HTTPSHandler(context=ctx),
45
+ urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
46
+ self.csrf = ''
47
+
48
+ def req(self, method, path, body=None):
49
+ rq = urllib.request.Request(CONTROLLER + path, method=method,
50
+ data=json.dumps(body).encode() if body is not None else None,
51
+ headers={'Content-Type': 'application/json'})
52
+ if self.csrf:
53
+ rq.add_header('x-csrf-token', self.csrf)
54
+ r = self.op.open(rq, timeout=15)
55
+ self.csrf = r.headers.get('x-csrf-token', self.csrf)
56
+ return json.load(r)
57
+
58
+ def login(self, user, pw):
59
+ self.req('POST', '/api/auth/login', {'username': user, 'password': pw})
60
+
61
+ def get_switch(self):
62
+ data = self.req('GET', '/proxy/network/api/s/default/stat/device')['data']
63
+ return next(d for d in data if d.get('mac') == SWITCH_MAC)
64
+
65
+ def set_poe(self, poe_mode):
66
+ # port_overrides is replaced wholesale on PUT — merge, never clobber.
67
+ sw = self.get_switch()
68
+ overrides = sw.get('port_overrides', [])
69
+ entry = next((o for o in overrides if o.get('port_idx') == PORT_IDX), None)
70
+ if entry is None:
71
+ entry = {'port_idx': PORT_IDX}
72
+ overrides.append(entry)
73
+ entry['poe_mode'] = poe_mode
74
+ self.req('PUT', f"/proxy/network/api/s/default/rest/device/{sw['_id']}",
75
+ {'port_overrides': overrides})
76
+ log(f'port {PORT_IDX} poe_mode -> {poe_mode}')
77
+
78
+ def stick_up():
79
+ # TCP probe of the Stick's own control port — proves the device booted,
80
+ # not just that the link is up. (The host has no ping binary anyway.)
81
+ try:
82
+ socket.create_connection((STICK_IP, 2431), timeout=2).close()
83
+ return True
84
+ except OSError:
85
+ return False
86
+
87
+ def main():
88
+ log(f'power-cycling Stick-DE3 (sw {SWITCH_MAC} port {PORT_IDX}, {OFF_SECONDS}s off)')
89
+ api = Unifi()
90
+ api.login(*load_creds())
91
+ api.set_poe('off')
92
+ try:
93
+ time.sleep(OFF_SECONDS)
94
+ finally:
95
+ # restore is sacred: retry hard so the port can never stay dark
96
+ for attempt in range(5):
97
+ try:
98
+ api.set_poe('auto')
99
+ break
100
+ except Exception as e:
101
+ log(f'restore attempt {attempt + 1} failed: {e}')
102
+ time.sleep(5)
103
+ try:
104
+ api.login(*load_creds())
105
+ except Exception:
106
+ pass
107
+ else:
108
+ log('FATAL: could not restore PoE — port may be off!')
109
+ sys.exit(2)
110
+
111
+ deadline = time.time() + BOOT_WAIT_S
112
+ while time.time() < deadline:
113
+ if stick_up():
114
+ log(f'Stick back up ({STICK_IP}:2431 accepting connections)')
115
+ return
116
+ time.sleep(5)
117
+ log(f'WARNING: Stick not reachable within {BOOT_WAIT_S}s of power restore')
118
+ sys.exit(1)
119
+
120
+ if __name__ == '__main__':
121
+ main()