import { describe, expect, it } from "vitest"; import { activeLowFrom, 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(); const radarOnI2 = new Set([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 }); }); describe("activeLowFrom (config → active-low terminal set)", () => { it("reads a config.inputs[] presence row with activeLow", () => { const set = activeLowFrom({ inputs: [ { input: 1, role: "button", relay: 1 }, { input: 2, role: "presence", relay: 1, kind: "radar", activeLow: true }, { input: 5, role: "presence", relay: 2, kind: "radar", activeLow: true }, // exit radar ], }); expect([...set].sort()).toEqual([2, 5]); // both radars inverted; the button is not }); it("still reads the LEGACY relays[].presenceActiveLow (back-compat)", () => { const set = activeLowFrom({ relays: [{ relay: 1, direction: "entry", presenceInput: 2, presenceActiveLow: true }], }); expect([...set]).toEqual([2]); }); it("honours an explicit top-level inputActiveLow escape hatch + merges all sources", () => { const set = activeLowFrom({ inputActiveLow: [3], inputs: [{ input: 2, role: "presence", activeLow: true }], relays: [{ relay: 1, direction: "entry", presenceInput: 4, presenceActiveLow: true }], }); expect([...set].sort()).toEqual([2, 3, 4]); }); });