refactor(setup): unify controller I/O — event-driven relays[] + generic inputs[]
The controller new/edit modal hardcoded both its outputs and its inputs, so an
operator could neither add a generic event-driven relay nor a free-standing input
(e.g. a second radar at the exit). This unifies both into symmetric, first-class
lists. Behaviour for existing booths is unchanged (back-compat, no DB migration).
Outputs — one event→action relays[] list:
- A relay is "when EVENT X happens, do its action": entry/exit/both pulse a
barrier; a new `radarAlert` event drives a non-barrier alert lamp (blink while
its trigger input is active, SOLID once the camera confirms a car).
- Dropped the separate config.buttonLight block — the lamp is just a relays[] row
with direction:"radarAlert" (triggerInput + blink cadence). `alertRelaysOf()`
replaces `buttonLightOf()`; ButtonLightController keeps its proven 3-state
machine (serialized UDP, fail-OFF, hot-reload), now keyed per controllerId:relay
so several alert lamps on one controller run independently. Every barrier
resolver skips radarAlert rows (no auto-open; barrier-not-a-door intact).
Inputs — one first-class config.inputs[] list (the twin of relays[]):
- Each row is { input, role, relay?, kind?, activeLow?, cooldownSec? } with a
"+ Add input" button. role ∈ button | presence | alertTrigger; button/presence
name the relay they serve. An exit radar is just another presence row.
- Keystone `inputsOf(row)`: returns config.inputs[] or SYNTHESIZES it from the
legacy relays[].button/presenceInput/... fields, so relayForButton /
relayForPresence resolve identically from either shape — zero-downtime, no
migration. entry-flow.ts is unchanged (resolves through the same functions).
- Fixed a latent bug this exposed: the alert lamp's camera lock was hardcoded to
the ENTRY camera. Added relays[].lockLane ("entry"|"exit", default entry); the
lamp now locks on its own lane's camera, so an exit radar's lamp tracks the exit
camera. button-light tracks both #entryBusy/#exitBusy.
- Driver: extracted activeLowFrom(config) — merges inputs[] activeLow, legacy
relays[].presenceActiveLow, and the inputActiveLow escape hatch.
UI: the relay dropdown gained a "Radar alert" option (reveals trigger/lock/blink
inputs); InputEditor is rewritten to a generic list (role select folds loop/radar);
i18n sq+en kept at type-parity.
Tests: new device-resolve.test.ts (inputs[] resolution + legacy fallback identical
+ exit-radar resolves to the exit relay); button-light gains a two-independent-
alert-relays case and an exit-lamp lockLane case; access-dingtian gains
activeLowFrom cases. Full workspace build/lint/test green (i18n parity included).
Wiki + memory updated (button-light-indicator, entry-double-press, dingtian-relay).
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
+19
-16
@@ -369,27 +369,30 @@ export const en: Catalog = {
|
||||
"Inputs are TERMINALS the host READS: the entry button and the presence/radar sensor. Each belongs to an entry barrier — it triggers or gates that relay.",
|
||||
inputsIdleHigh: "Inputs idle HIGH",
|
||||
inputsIdleHighHint: "This board idles inputs HIGH (status 1111); a press pulls LOW.",
|
||||
inputsForRelay: "For relay {{relay}}",
|
||||
inputsNoEntryRelay: "No entry relay — add an 'Entry' or 'Entry + exit' relay in Outputs to assign terminals.",
|
||||
relay: "Relay",
|
||||
entryButtonTerminal: "Entry button on terminal",
|
||||
presenceInput: "Presence sensor (terminal)",
|
||||
presenceInputHint:
|
||||
"Input terminal the vehicle-presence sensor (induction loop or radar) is wired to. When set, exactly ONE ticket issues per car: the button prints only while a car is present, and no second ticket issues until the sensor clears (the car drove in) and a new car re-occupies it. Preferred mode.",
|
||||
// Generic input rows: terminal + role + the relay it serves.
|
||||
inputTerminal: "Terminal",
|
||||
inputServesRelay: "Serves relay",
|
||||
roleButton: "Entry button",
|
||||
rolePresenceLoop: "Presence (loop)",
|
||||
rolePresenceRadar: "Presence (radar)",
|
||||
roleAlertTrigger: "Alert trigger",
|
||||
addInput: "+ Add input",
|
||||
entryCooldown: "Cooldown after ticket (s)",
|
||||
entryCooldownHint:
|
||||
"When there's no presence sensor: repeat button presses are suppressed for this many seconds after a ticket. A fallback (not a guarantee) — a determined abuser can wait it out.",
|
||||
presenceKind: "Kind",
|
||||
presenceKindLoop: "Loop",
|
||||
presenceKindRadar: "Radar",
|
||||
presenceActiveLow: "Active-low",
|
||||
presenceActiveLowHint:
|
||||
activeLow: "Active-low",
|
||||
activeLowHint:
|
||||
"Tick if the presence sensor (e.g. a radar) idles HIGH and goes LOW on detection — the opposite of the button. This inverts that terminal's reading so 'present' is read correctly.",
|
||||
buttonLight: "Button light (spare relay)",
|
||||
buttonLightRelay: "Relay",
|
||||
buttonLightHint:
|
||||
"The button's 12 V light on a spare relay. Blinks when the radar detects but the camera doesn't confirm a car; solid on when both confirm; off otherwise.",
|
||||
buttonLightBarrierWarn: "This relay is used by a barrier — pick a spare relay.",
|
||||
eventRadarAlert: "Radar alert (lamp)",
|
||||
triggerInput: "Trigger input",
|
||||
triggerInputHint:
|
||||
"The input terminal (the radar) that starts this relay blinking. Blinks while the trigger is active but the camera doesn't confirm a car; solid on once the camera confirms; off otherwise.",
|
||||
lockLane: "Lock from",
|
||||
lockLaneHint:
|
||||
"Which camera locks the lamp solid: the entry or the exit camera. An exit radar must lock on the EXIT camera.",
|
||||
lockLaneEntry: "Entry camera",
|
||||
lockLaneExit: "Exit camera",
|
||||
blinkOnMs: "Blink on (ms)",
|
||||
blinkOffMs: "Blink off (ms)",
|
||||
addRelay: "+ Add relay",
|
||||
|
||||
+19
-16
@@ -378,27 +378,30 @@ export const sq = {
|
||||
"Hyrjet janë TERMINALE që hosti i LEXON: butoni i hyrjes dhe sensori i pranisë/radari. Secila i përket një barriere hyrëse — e gateron ose e nis atë rele.",
|
||||
inputsIdleHigh: "Hyrjet në pushim HIGH",
|
||||
inputsIdleHighHint: "Kjo pllakë i mban hyrjet HIGH në pushim (statusi 1111); një shtypje e ul në LOW.",
|
||||
inputsForRelay: "Për rele {{relay}}",
|
||||
inputsNoEntryRelay: "Asnjë rele hyrëse — shto një rele 'Hyrje' ose 'Hyrje + dalje' te Daljet që të caktosh terminalet.",
|
||||
relay: "Rele",
|
||||
entryButtonTerminal: "Butoni i hyrjes në terminalin",
|
||||
presenceInput: "Sensori i pranisë (terminali)",
|
||||
presenceInputHint:
|
||||
"Terminali hyrës ku është lidhur sensori/laku i pranisë së automjetit. Kur vendoset, lëshohet vetëm NJË biletë për automjet: butoni printon vetëm kur ka makinë, dhe nuk lëshon biletë të dytë derisa laku të lirohet (makina hyri) dhe një makinë e re ta zërë. Mënyra e preferuar.",
|
||||
// Generic input rows: terminal + role + the relay it serves.
|
||||
inputTerminal: "Terminali",
|
||||
inputServesRelay: "I shërben reles",
|
||||
roleButton: "Butoni i hyrjes",
|
||||
rolePresenceLoop: "Prania (lak induktiv)",
|
||||
rolePresenceRadar: "Prania (radar)",
|
||||
roleAlertTrigger: "Trigger alarmi",
|
||||
addInput: "+ Shto hyrje",
|
||||
entryCooldown: "Pritje pas biletës (sek)",
|
||||
entryCooldownHint:
|
||||
"Kur nuk ka sensor pranie: shtypjet e përsëritura të butonit shtypen për kaq sekonda pas një bilete. Zgjidhje rezervë (jo garanci) — një abuzues mund ta presë afatin.",
|
||||
presenceKind: "Lloji",
|
||||
presenceKindLoop: "Lak",
|
||||
presenceKindRadar: "Radar",
|
||||
presenceActiveLow: "Aktiv-ulët",
|
||||
presenceActiveLowHint:
|
||||
activeLow: "Aktiv-ulët",
|
||||
activeLowHint:
|
||||
"Shëno nëse sensori i pranisë (p.sh. radari) qëndron HIGH në pushim dhe shkon LOW kur detekton — e kundërta e butonit. Kjo përmbys leximin e atij terminali që 'prania' të lexohet saktë.",
|
||||
buttonLight: "Drita e butonit (rele rezervë)",
|
||||
buttonLightRelay: "Rele",
|
||||
buttonLightHint:
|
||||
"Drita 12V e butonit e lidhur në një rele rezervë. Pulson kur radari detekton por kamera s'konfirmon makinë; ndizet fiks kur të dy konfirmojnë; përndryshe fiket.",
|
||||
buttonLightBarrierWarn: "Kjo rele përdoret nga një barrierë — zgjidh një rele rezervë.",
|
||||
eventRadarAlert: "Alarm radar (dritë)",
|
||||
triggerInput: "Trigger input",
|
||||
triggerInputHint:
|
||||
"Terminali i hyrjes (radari) që nis pulsimin e kësaj rele. Pulson kur Trigger input është aktiv por kamera s'konfirmon makinë; ndizet fiks kur kamera konfirmon; përndryshe fiket.",
|
||||
lockLane: "Bllokimi nga",
|
||||
lockLaneHint:
|
||||
"Cila kamerë e ndez dritën fiks: hyrja apo dalja. Një radar i daljes duhet të bllokohet nga kamera e DALJES.",
|
||||
lockLaneEntry: "Kamera e hyrjes",
|
||||
lockLaneExit: "Kamera e daljes",
|
||||
blinkOnMs: "Pulsim ndezur (ms)",
|
||||
blinkOffMs: "Pulsim fikur (ms)",
|
||||
addRelay: "+ Shto rele",
|
||||
|
||||
Reference in New Issue
Block a user