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:
@@ -6,6 +6,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { createDb, deviceEvents as deviceEventsTable, type Db } from "@parking/db";
|
||||
import { TOKEN_COOKIE, requireJwtSecret, initAuth } from "./auth.js";
|
||||
import { deviceEvents } from "./device-events.js";
|
||||
import { ButtonLightController } from "./button-light.js";
|
||||
import { EntryFlow } from "./entry-flow.js";
|
||||
import { EventLog } from "./event-log.js";
|
||||
import { ExitFlow } from "./exit-flow.js";
|
||||
@@ -188,6 +189,13 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
});
|
||||
app.addHook("onClose", async () => unsubscribeEntry());
|
||||
|
||||
// Button-light indicator: drives the entry button's lamp on a spare relay from the
|
||||
// RADAR input vs. the camera lane status (blink = radar-only, solid = radar+camera,
|
||||
// off otherwise). A non-barrier aux output; fails OFF. See button-light.ts.
|
||||
const buttonLight = new ButtonLightController(db, app.log);
|
||||
buttonLight.start();
|
||||
app.addHook("onClose", async () => buttonLight.stop());
|
||||
|
||||
// Read-driven flows: a credential read (ticket scan / plate / card) routes via the
|
||||
// dispatcher to either the SUBSCRIPTION flow (if it matches a subscription) or the
|
||||
// transient EXIT flow. See read-dispatch.ts, exit-flow.ts, subscription-flow.ts,
|
||||
|
||||
Reference in New Issue
Block a user