Files
parking_solution/wiki/decisions/access-controller-button-flow.md
T
julian 77606da2c9 UHPPOTE hardware bring-up + entry-flow blocker
Brought up the real UHPPOTE controller (serial 225088491, fw 09120) end to end
and recorded a procurement-level blocker.

Verified on hardware:
- discovery (LAN scan), host-commanded openDoor on doors 1 & 2 (physically
  actuated; reason="remote open door"), and live button capture
  (reason="push button ok").

Driver/networking fixes (packages/devices/src/drivers/access-uhppote.ts):
- broadcast to subnet-directed address (lib doesn't enable SO_BROADCAST for the
  global 255.255.255.255 -> EACCES);
- Config broadcast must match the target's subnet for unicast reply routing
  (fixes the health-check timeout: 5s -> 24ms ready);
- discover across all local subnets, dedupe by serial;
- serialize all controller I/O (concurrent calls collided on UDP :60001).

Server/UX:
- load .env via node --env-file-if-exists (vars weren't being read before);
- SETUP_AUTH_BYPASS hardened: env-gated, dev + loopback only, fails closed
  otherwise; surfaced as catalog.authBypass so the wizard drops the token field;
- .env.example documents all vars; inline favicon stops a 404.
- apps/server/scripts/: uhppote-listen (live events, restores prior listener)
  and uhppote-relay (guarded door-open test).

BLOCKER (wiki/decisions/access-controller-button-flow.md): the controller's
push-button input auto-opens the relay in firmware with no report-without-open
mode, so ticket-first entry (button -> print -> open, fail-closed) is impossible
as wired. UHPPOTE can't do it on that input; ZKTeco *might* via a programmable
aux input + PULL SDK but that's unverified and needs a new driver. Entry-lane
hardware decision paused to focus on the business side.

wiki: access-controller-button-flow (blocker), zkteco-controller (stub +
assessment), uhppote-controller callout, index + log.
2026-06-14 10:29:43 +02:00

4.8 KiB

type, tags, sources, updated, status
type tags sources updated status
decision
parking
hardware
access-control
blocker
open
parking-system-architecture
2026-06-15 open

Blocker: Push-Button → Auto-Open Defeats the Ticket-First Entry Flow

Procurement-blocking finding (2026-06-15), from on-hardware testing. The UHPPOTE and ZKTeco access controllers on hand cannot, as wired/configured, deliver the required entry flow. This blocks the entry lane and needs a hardware/wiring resolution before that lane ships. Work paused here to focus on the business side. See entry-exit-readers, trust-boundary.

The required flow

car arrives → driver presses button → TICKET PRINTS → then barrier opens

The ticket must print before the barrier opens, and entry must fail closed: if the ticket can't print (printer offline / out of paper), the barrier must stay shut — no untracked car enters (the anti-fraud core, see threat-model, append-only-event-chain).

The problem (verified on real hardware)

The push-button is wired into the controller's push-button / request-to-exit (REX) input. On that input the controller firmware auto-fires the relay immediately — the door opens on press. The host only observes the event after the relay has already actuated, so there is no point at which it can insert the ticket-print step. The host is structurally too late.

This is the entry-exit-readers principle in the negative: a button on the controller's own input is decided by the controller, not the host. For host-in-the-loop entry, the button must be a host-side input and the controller demoted to a commanded relay.

Verified facts (UHPPOTE 225088491, firmware 09120)

Tested live via the uhppoted lib (see apps/server/scripts/):

  • Relays work, host-commanded: openDoor doors 1 & 2 → {opened:true}, physically actuated, logged as events with reason="remote open door".
  • Buttons fire events — but only after auto-opening: press logs an event with reason="push button ok" (door 1 button → door 1, door 2 button → door 2).
  • Both doors are in control: "controlled" mode with a 3 s delay.
  • No UDP command exists to stop the push-button input from auto-opening the relay. The uhppoted protocol (and the UHPPOTE firmware, which is not changeable — uhppote-udp-protocol) has no "report-but-don't-open" mode for that input.

Per-device assessment

  • UHPPOTE — push-button input is firmware-hardwired to auto-open; not configurable. Cannot do ticket-first entry while the button is on that input. Fine as a commanded relay (host openDoor) and for permit/wiegand lanes; not suitable for the button-driven entry lane without rewiring the button to a host-side input. See uhppote-controller.
  • ZKTeco (C3 / inBio) — better positioned but unverified by us. Its auxiliary inputs have programmable linkage (via ZKBioSecurity / the PULL SDK) and need not be tied to "open door", and its SDK streams real-time events + an explicit open command — so button → aux input → host event → print → host opens is achievable in principle. BUT: (a) wiring the button to the door's exit-switch input still auto-opens, same as UHPPOTE — it only works on a properly-configured aux input; (b) ZKTeco speaks its own PULL SDK protocol, not the UHPPOTE one — no verified MIT-licensed Node lib exists (mature open implementations are Python: zkaccess-c3-py, pyzkaccess), so it needs a new driver. See zkteco-controller.

Bottom line: neither controller currently on hand delivers ticket-first entry as wired. UHPPOTE can't at all on that input; ZKTeco might with an aux-input reconfig + a new driver, but that is unverified.

Options (unresolved — to settle later)

  1. Rewire the button off the controller's REX/exit input to a host-readable input (spare GPIO, a USB/IP digital-input module, or a non-auto-open input) so the host sees the press, prints, then commands openDoor. Keeps UHPPOTE as a commanded relay.
  2. ZKTeco aux-input path — wire the button to a programmable aux input, host decides via SDK. Requires building a [[zkteco-controller|zkteco]] driver (PULL SDK) and validating the no-auto-open linkage on real hardware.
  3. Custom esp32-custom-controller controller for the entry lane — full control of button logic; the documented (currently deferred) prevention-grade path.

Status

Open / paused. Recorded so the constraint isn't rediscovered. The relay + event-log command path is otherwise proven on hardware (discovery, open, event capture all work) — the gap is specifically the button-before-ticket ordering on the entry lane. Revisit when the entry-lane hardware decision is taken. Related: open-questions (lane topology, failure modes).