Files
parking_solution/wiki/concepts/button-light-indicator.md
T
julian 2915d141aa feat(devices): radar presence input + button-light output on the controller
Model the entry button (I1) and a Hikvision radar (I2) as named children of the
access controller, and drive the button's 12V lamp on a spare relay.

- Radar = the existing relays[].presenceInput one-car-one-ticket gate, now labelled
  presenceKind: loop|radar. A radar may idle opposite the button, so add a per-input
  active-level override: relays[].presenceActiveLow -> driver inputActiveLow set,
  inverting just that terminal (pure helper inputActive()). The Dingtian has one
  board-wide resting level otherwise.
- AuxOutputDevice.setAux(channel,on) capability on the device interface (Dingtian
  latch) so business logic drives a NON-barrier lamp through the interface. Barriers
  still only pulseOpen — barrier-not-a-door preserved.
- ButtonLightController: subscribes to the radar input edge + the camera lane status
  and drives a 3-state lamp — radar+car=solid, radar-only=blink (~1Hz), else off.
  Fails OFF on host loss/error; de-duped. A radar detection never opens a barrier on
  its own (advisory; threat model).
- SetupWizard: presence kind + active-low + a button-light relay picker; sq+en i18n.

Tests: button-light.test.ts (truth table + blink + fail-OFF + de-dupe),
access-dingtian.test.ts (active-level inversion). Workspace build+lint+test green
(158 server tests). Wiki: hikvision-radar, button-light-indicator + updates.

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

63 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: concept
tags: [parking, device, indicator, radar, camera, aux-output, barrier-not-a-door]
sources: []
updated: 2026-06-24
status: 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|Dingtian]] 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|one-car-one-ticket]] gate observes — so the lamp and the gate always
agree on "a car is here").
- **Camera "car in zone"** = the existing **[[lpr-camera|lane status]]** (`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).
- **De-duped.** Only writes when the effective output changes, so the 50 ms input poll doesn't spam
the controller over UDP.
## Status
Built 2026-06-24 for the first booth (button I1, radar I2, lamp on a spare relay). Covered by
`apps/server/src/button-light.test.ts` (the truth table + blink toggling + fail-OFF + de-dupe).
Related: [[hikvision-radar]], [[entry-double-press]], [[lpr-camera]], [[dingtian-relay]],
[[barrier-not-a-door]].