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.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
---
|
||||
type: decision
|
||||
tags: [parking, hardware, access-control, blocker, open]
|
||||
sources: [parking-system-architecture]
|
||||
updated: 2026-06-15
|
||||
status: 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|ESP32]] 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).
|
||||
@@ -11,6 +11,16 @@ The starting access-control hardware: a **UHPPOTE Wiegand 26/34 network controll
|
||||
a cheap reader-plus-relay frontend, acceptable **provided you understand its limits**. The plan
|
||||
is UHPPOTE now → ZKTeco later (see [[bom]]). (See [[parking-system-architecture]] §6.)
|
||||
|
||||
> **⚠️ Entry-flow blocker (verified on hardware):** the push-button input **auto-opens the relay
|
||||
> in firmware** — there's no command to make it report-without-opening — so it **cannot** do
|
||||
> ticket-first entry (`button → print → open`). Fine as a host-**commanded relay** and for
|
||||
> [[wiegand]]/permit lanes, but **not** the button-driven entry lane as wired. Full detail and
|
||||
> options: [[access-controller-button-flow]].
|
||||
>
|
||||
> **Verified working on the real unit** (serial 225088491, fw 09120): host-commanded `openDoor`
|
||||
> on doors 1 & 2 (physically actuated, `reason="remote open door"`); button presses captured live
|
||||
> (`reason="push button ok"`); [[device-discovery]] scan. Test scripts: `apps/server/scripts/`.
|
||||
|
||||
> **Implementation:** integrated via the official **`uhppoted`** npm package (MIT, by the
|
||||
> `uhppoted` org — `github.com/uhppoted/uhppoted-lib-nodejs`), added to `@parking/devices` as the
|
||||
> `uhppote` access driver ([[device-registry]]). It exposes exactly the protocol commands this
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
type: entity
|
||||
tags: [parking, hardware, access-control]
|
||||
sources: [parking-system-architecture]
|
||||
updated: 2026-06-15
|
||||
---
|
||||
|
||||
# ZKTeco Controller
|
||||
|
||||
A network access controller (C3 / inBio families) — the documented "UHPPOTE now → ZKTeco later"
|
||||
upgrade in the [[bom]]. A `zkteco` driver **stub** exists in the [[device-registry]] but the
|
||||
**real protocol is not implemented** (see below).
|
||||
|
||||
## Relevance to the entry-flow blocker
|
||||
|
||||
ZKTeco is **better positioned** than the [[uhppote-controller]] for host-in-the-loop entry (the
|
||||
[[access-controller-button-flow]] blocker), but this is **unverified on our hardware**:
|
||||
|
||||
- Its **auxiliary inputs** have **programmable linkage** (ZKBioSecurity software / PULL SDK) and
|
||||
are **not** hardwired to "open door" — so a button on an *aux* input can raise a host event
|
||||
**without** auto-opening, enabling `button → host → print ticket → host opens`.
|
||||
- The **PULL SDK** streams real-time events (`GetRTLog`) and has an explicit open command
|
||||
(`ControlDeviceOutput`).
|
||||
- Caveat: a button on the **door's exit-switch input still auto-opens** (same as UHPPOTE) — only
|
||||
the **aux-input** path avoids it.
|
||||
|
||||
## Driver status
|
||||
|
||||
- ZKTeco speaks its **own PULL SDK / TCP protocol** — **not** the UHPPOTE UDP protocol, so the
|
||||
`uhppoted` lib does **not** work with it.
|
||||
- No verified MIT/Apache/BSD **Node** library found. Mature open implementations are **Python**
|
||||
(`zkaccess-c3-py`, `pyzkaccess`). Adopting ZKTeco means **writing a new driver** for the
|
||||
registry ([[device-adapter-pattern]]) — real protocol work.
|
||||
|
||||
## Reference
|
||||
|
||||
- ZKTeco SDK / PULL SDK: https://www.zkteco.com/en/SDK
|
||||
- `zkaccess-c3-py`: https://github.com/vwout/zkaccess-c3-py
|
||||
- `pyzkaccess`: https://github.com/bdragon300/pyzkaccess
|
||||
|
||||
See [[access-controller-button-flow]] for the full blocker context.
|
||||
@@ -37,6 +37,7 @@ Counts: 1 source · 14 entities · 10 concepts · 2 decision records.
|
||||
- [[atecc608]] — secure element; non-extractable signing key (host events + controller auth).
|
||||
- [[wiegand]] — reader standard feeding the controller directly (autonomous permit-holder path).
|
||||
- [[lpr-camera]] — edge-AI plate recognition; host-side casual-identity source.
|
||||
- [[zkteco-controller]] — C3/inBio controller; aux-input path may enable host-in-the-loop (driver TBD).
|
||||
- [[bom]] — reference bill of materials (barrier, loops, controller, readers, payment, host, network).
|
||||
|
||||
## Concepts — foundational forces
|
||||
@@ -68,3 +69,4 @@ Counts: 1 source · 14 entities · 10 concepts · 2 decision records.
|
||||
## Decisions
|
||||
- [[standing-decisions]] — settled decisions (stack, platform, integrity, access control, readers).
|
||||
- [[open-questions]] — 7 open items (6 procurement + JWT key choice); ESP32 device auth deferred.
|
||||
- [[access-controller-button-flow]] — ⚠️ BLOCKER: UHPPOTE/ZKTeco on hand can't do ticket-first entry as wired.
|
||||
|
||||
+20
@@ -52,3 +52,23 @@ health badges and auto-fills serial + host on selection. Verified: catalog flags
|
||||
uhppote; discover runs and fails gracefully without hardware (broadcast EACCES);
|
||||
non-discoverable driver → 400; no token → 401. Modeled generically so cameras
|
||||
(ONVIF) can add discovery later.
|
||||
|
||||
## [2026-06-15] test+blocker | UHPPOTE hardware bring-up + entry-flow blocker
|
||||
Brought up the real UHPPOTE (serial 225088491, fw 09120) end to end. Fixed the
|
||||
networking path: WSL2 mirrored mode, then driver bugs — subnet-directed broadcast
|
||||
(the lib doesn't enable SO_BROADCAST for global 255.255.255.255), broadcast must
|
||||
match the target's subnet for unicast reply routing (health-check timeout fix),
|
||||
multi-subnet discovery, and serialized I/O (concurrent calls collided on :60001).
|
||||
Added .env loading (Node --env-file), env-gated+fail-closed SETUP_AUTH_BYPASS, and
|
||||
an authBypass flag so the wizard drops the token field. Test scripts in
|
||||
apps/server/scripts/ (uhppote-listen, uhppote-relay).
|
||||
|
||||
VERIFIED on hardware: discovery; host-commanded openDoor doors 1&2 (physical +
|
||||
reason="remote open door"); button presses live (reason="push button ok").
|
||||
|
||||
BLOCKER FOUND: the controller push-button input auto-opens the relay in firmware —
|
||||
no command to report-without-opening — so ticket-first entry (button→print→open)
|
||||
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.
|
||||
Recorded in [[access-controller-button-flow]] + [[zkteco-controller]]. Entry-lane
|
||||
hardware decision paused to focus on the business side.
|
||||
|
||||
Reference in New Issue
Block a user