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
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { inputActive } from "./access-dingtian.js";
|
||||
|
||||
// Per-input active-level normalisation. The board has ONE resting level, but a radar
|
||||
// can idle opposite the button — listing its terminal in `activeLow` inverts just that
|
||||
// input so "present" reads correctly. See wiki/entities/hikvision-radar.md.
|
||||
|
||||
describe("inputActive (per-input active-level)", () => {
|
||||
const none = new Set<number>();
|
||||
const radarOnI2 = new Set<number>([2]);
|
||||
|
||||
it("default board (resting HIGH): a pull LOW is active, HIGH is rest", () => {
|
||||
// Button on I1, board idles HIGH → active when LOW.
|
||||
expect(inputActive(false, 1, true, none)).toBe(true); // LOW = pressed
|
||||
expect(inputActive(true, 1, true, none)).toBe(false); // HIGH = rest
|
||||
});
|
||||
|
||||
it("resting LOW board: a pull HIGH is active", () => {
|
||||
expect(inputActive(true, 1, false, none)).toBe(true);
|
||||
expect(inputActive(false, 1, false, none)).toBe(false);
|
||||
});
|
||||
|
||||
it("active-low override inverts ONLY the listed input", () => {
|
||||
// Board idles HIGH (button on I1), radar on I2 idles HIGH and goes LOW on detect →
|
||||
// mark I2 active-low so detection (LOW) reads active.
|
||||
// I1 (button) keeps the board default:
|
||||
expect(inputActive(false, 1, true, radarOnI2)).toBe(true); // button LOW = active
|
||||
expect(inputActive(true, 1, true, radarOnI2)).toBe(false);
|
||||
// I2 (radar) overridden to active-low: active when LOW.
|
||||
expect(inputActive(false, 2, true, radarOnI2)).toBe(true); // radar LOW = detecting
|
||||
expect(inputActive(true, 2, true, radarOnI2)).toBe(false); // radar HIGH = clear
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user