- dingtian-relay: the "offline despite ping" gotcha (relay_pw in every binary frame, missing form field → Test connection sent 0 → timeout) + the identity-gated secret re-merge that stops a redirected probe exfiltrating the password. - button-light-indicator: serialized desired-state worker (UDP is unordered → the lamp stuck on/off) and hot-reload of the lamp config (no restart). - log entry for the three fixes (commits420542c/fd15988/830993b). Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
4.3 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| concept |
|
2026-06-24 | settled |
Button-light indicator (radar × camera disagreement lamp)
The entry button has a 12 V light. It is driven by the host on a spare relay of the dingtian-relay controller as a 3-state indicator that combines the [[hikvision-radar| radar]] input with the camera "car in zone" signal:
| Radar input | Camera (lane entry busy) | Button light |
|---|---|---|
| detecting | free — no car confirmed | BLINK (~1 Hz) |
| detecting | busy — camera confirms a car | SOLID on |
| clear | — | OFF |
It is a disagreement indicator: the radar sees something but the camera hasn't confirmed a real vehicle → blink (attention / "pull forward"); both agree → solid; nothing there → off.
Signals
- Radar = the presence input edge on the entry relay (
relays[].presenceInput, the same edge the entry-double-press gate observes — so the lamp and the gate always agree on "a car is here"). - Camera "car in zone" = the existing lpr-camera (
LaneStatusEvententry busy/free, from camera vehicle detection). Already advisory; already drives the booth's barrier lights. No new camera plumbing.
Config
A controller-level config.buttonLight = { relay, blinkOnMs?, blinkOffMs? } (the operator picks a
spare relay — not a barrier relay; the setup UI warns if it overlaps one). Blink defaults to
500 ms / 500 ms.
Implementation
apps/server/src/button-light.ts — ButtonLightController subscribes to deviceEvents.onInput
(radar) + onLaneStatus (camera), computes the target state per controller, and drives the lamp via
a device-agnostic aux-output capability.
- Aux-output capability.
AuxOutputDevice { setAux(channel, on) }on the device interface (the Dingtian driver implements it as a latch). Business logic drives the lamp through this — never the driver's barrier methods. - Barrier-not-a-door is preserved. The lamp is not a barrier, so holding / blinking it on a
timer is fine — the barrier-not-a-door rule forbids timing a barrier closed, and barriers
still only ever
pulseOpen. The lamp uses the separatesetAuxlatch. - Fails OFF. On host loss, shutdown, or a
setAuxerror the lamp defaults OFF — a dead lamp is "no hint", never a misleading solid "go". SOLID is only ever held while busy + present is actively true (never latched on through a crash path). - Serialized sends (must — UDP is unordered). The first cut fired fire-and-forget
setAuxevery 500 ms; over unordered UDP the on/off packets reordered/overlapped and the relay latched on whichever packet the device processed last — the lamp got stuck on/off at random (observed on hardware). Fix: a desired-state + serialized worker (#pump). The blink timer only flips adesiredOnflag; the worker guarantees one in-flight send per lamp and, on completion, re-converges to the latest desired state. So the final state is always authoritative and a lost/stale packet self-corrects. This also de-dupes (it skips a send whenconfirmedOn === desiredOn), so the input stream never spams the controller. - Hot-reloads the config (no restart). The lamp map is reconciled against the live device config at start AND before each event (mirroring device-status-monitoring, which re-reads the device set each tick) — adding/updating/dropping lamps. So a button light added or re-pointed in the setup UI takes effect on the next radar edge, not after a server restart. (The first cut loaded the map once at boot, so a just-saved lamp silently did nothing until restart.)
Status
Built 2026-06-24 for the first booth (button I1, radar I2, lamp on a spare relay); the serialized-send
- hot-reload fixes landed the same day after the lamp stuck on/off on hardware. Covered by
apps/server/src/button-light.test.ts(the truth table, blink toggling asserted on the device's confirmed state, fail-OFF, de-dupe, and a lamp-added-after-start reconcile case). Related: hikvision-radar, entry-double-press, lpr-camera, dingtian-relay, barrier-not-a-door.