Files
parking_solution/wiki/concepts/button-light-indicator.md
T
julian 5a5fedf4f4 docs(wiki): booth bring-up fixes — relay password, secret re-merge, lamp concurrency
- 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 (commits 420542c / fd15988 / 830993b).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-24 19:11:49 +02:00

4.3 KiB
Raw Blame History

type, tags, sources, updated, status
type tags sources updated status
concept
parking
device
indicator
radar
camera
aux-output
barrier-not-a-door
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 (LaneStatusEvent entry 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 separate setAux latch.
  • Fails OFF. On host loss, shutdown, or a setAux error 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 setAux every 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 a desiredOn flag; 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 when confirmedOn === 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