23919164ee
The device pushes button events to the backend via its Input Link URL feature;
the backend decides. No polling — the chosen entry architecture.
packages/devices:
- dingtian driver: configureInputPush() writes the device's input_link_url
config (per-input server/port/path, en=1, active-LOW, plain HTTP) so each
input HTTP-GETs the backend on press/release. Extracted #readConfig/#writeConfig
(with the required command:setconfig injection + post-write reset tolerance).
apps/server:
- routes/devices.ts: public GET/POST
/api/devices/dingtian/:deviceId/input/:n/{on,off} — translates a device push
into an internal device event. Not behind cookie/CSRF (machine call from the
device); trust comes from the signed event log, not this request.
- device-events.ts: internal EventEmitter bus so the entry flow subscribes to
input events without coupling to HTTP. Wired into the server.
Verified on hardware: configured the device, then real presses on all 4 inputs
pushed to the backend (input N on+off, source = device IP). No polling.
wiki: device-input-flow concept (path + trust model for the flat/no-VLAN
network); dingtian-relay updated; index + log.
148 lines
9.9 KiB
Markdown
148 lines
9.9 KiB
Markdown
# Wiki Log
|
|
|
|
Append-only chronological record. Each entry: `## [YYYY-MM-DD] <op> | <subject>`.
|
|
Query with `grep "^## \[" log.md | tail -5`.
|
|
|
|
## [2026-06-14] ingest | Parking System — Architecture & Design Notes
|
|
First source ingested. Bootstrapped wiki scaffolding (CLAUDE.md schema, index.md,
|
|
overview.md, log.md). Created source summary, 14 entity pages, 9 concept pages, and
|
|
decision records (settled decisions + 6 open questions). Source is a dense design
|
|
doc covering stack, threat model, device architecture, UHPPOTE access control, the
|
|
custom ESP32 controller alternative, readers, and a reference BOM.
|
|
|
|
## [2026-06-15] decision | JWT key choice + ESP32 deferred
|
|
From app work, not a new source. Added [[open-questions]] #7 (symmetric vs.
|
|
asymmetric JWT signing key — raised by the commit security review; prefer RS256/EdDSA
|
|
so verifying hosts hold only a public key, mirroring the ATECC608 / challenge-response
|
|
property). Marked [[esp32-custom-controller]] `status: deferred` per decision not to
|
|
implement device-level auth for now (access control stays on UHPPOTE + network
|
|
isolation); noted in [[open-questions]] #6. Updated [[local-jwt-auth]] (hardened secret
|
|
handling + 8h expiry, asymmetric-key pointer) and the index.
|
|
|
|
## [2026-06-15] decision | Device-agnostic registry + first-run setup
|
|
From app work. Made the [[device-adapter-pattern]] selectable: added a
|
|
[[device-registry]] (catalog of drivers per category) and a [[first-run-setup]]
|
|
flow so the admin picks a device per lane at install. Categories: access
|
|
(ZKTeco / ESP32 relay), reader (Wiegand / TCP-IP), camera (Hikvision / Dahua,
|
|
snapshot-on-event), printer. Added a `CameraDevice` interface; new `lane_devices`
|
|
+ `setup_state` tables (migration 0001); admin-only setup endpoints. Stub drivers
|
|
for now (no real vendor protocols yet). Verified catalog + assign + validation +
|
|
auth end to end.
|
|
|
|
## [2026-06-15] decision | UHPPOTE library chosen + real driver
|
|
Researched Node options for the UHPPOTE controller. Chose the official
|
|
**`uhppoted`** npm package (MIT, actively maintained, full API incl. openDoor,
|
|
get-event(s)/event-index, set-listener, restore-default — covers the whole
|
|
[[event-log-ingestion]] design). Rejected: raw-dgram DIY (reinvents the lib),
|
|
node-red-contrib-uhppoted (wrong model), Go REST sidecar (extra runtime). Added
|
|
it to @parking/devices and implemented a real `uhppote` [[uhppote-controller]]
|
|
access driver (pulseOpen→openDoor, healthCheck→getStatus), registered in the
|
|
catalog. CJS interop: default-import + destructure. Verified it builds, appears
|
|
in the catalog, and degrades to "offline" gracefully without hardware. Real
|
|
on-VLAN test still pending.
|
|
|
|
## [2026-06-15] feature | Device discovery (UHPPOTE scan in setup)
|
|
The frontend had no way to find a UHPPOTE — but the controllers self-announce via
|
|
UDP broadcast. Added a generic [[device-discovery]] capability: optional
|
|
`DiscoverableDriver.discover()` on the registry, implemented by the `uhppote`
|
|
driver via `getDevices`. New admin-only `GET /api/setup/discover/:driverId`
|
|
(health-checks each found device); catalog now returns a `discoverable` list.
|
|
SetupWizard gains a "Scan for controllers" button that lists found devices with
|
|
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.
|
|
|
|
## [2026-06-15] feature | Cookie-based auth/authz (login, CSRF)
|
|
Built real authentication: bcrypt login → JWT in an HttpOnly+SameSite=Strict
|
|
cookie, readable CSRF cookie + X-CSRF-Token header (double-submit) on mutations,
|
|
role-guarded routes. Routes: /api/auth/{login,logout,me}. First admin seeded via
|
|
`pnpm --filter @parking/server seed-admin`. Removed the SETUP_AUTH_BYPASS shim
|
|
and the wizard token field; the SPA gates on /api/auth/me and only shows setup to
|
|
admins. Same-origin via the Vite dev proxy and a new prod nginx config
|
|
(deploy/nginx.conf). Verified end to end (curl + browser): wrong pass→401,
|
|
login→cookies set, me→admin, assign without CSRF→403 / with→201, no cookie→401,
|
|
session persists across reload. Updated [[local-jwt-auth]].
|
|
|
|
## [2026-06-15] lint+docs | Dev-environment pages (WSL networking, workflow)
|
|
Captured hard-won dev knowledge that was only in commit messages: new
|
|
[[wsl-dev-networking]] (WSL2 NAT blocks UDP broadcast → mirrored mode + the
|
|
multi-interface / subnet-broadcast / IPv6-localhost gotchas that remained) and
|
|
[[local-dev-workflow]] (setup, seed:admin, the dev-server-hang from the broken
|
|
strip-types script → tsx, the 127.0.0.1 proxy fix, .env loading). Corrected the
|
|
earlier "broadcast permission (EACCES)" note in [[device-discovery]] — the real
|
|
cause was the lib not enabling SO_BROADCAST for the global 255.255.255.255;
|
|
documented the three verified broadcast gotchas + serialization. Added a `reference`
|
|
page type to the schema; new "Dev environment" index section.
|
|
|
|
## [2026-06-15] decision | Dingtian relay chosen; HTTP over MQTT; unmanned direction
|
|
New relay+input controller on hand (Dingtian 4ch). Its inputs are decoupled from
|
|
relays (configurable via input_link_relay) — solves the [[access-controller-button-flow]]
|
|
blocker the UHPPOTE couldn't. Transport decision [[dingtian-vs-mqtt]]: direct
|
|
HTTP/UDP now (UDP string for relay control on :60001; device input_link_url HTTP
|
|
push for button events), MQTT skipped (broker = infra + failure mode + overkill at
|
|
this scale) but kept for later multi-lane scale. Recorded the stated roadmap to
|
|
**fully unmanned, no-booth** operation in [[autonomous-direction]] and its threat-model
|
|
shift (operator-fraud → unattended-machine threats). New stub [[dingtian-relay]]
|
|
with the full protocol from the SDK. Driver + on-hardware test still to build.
|
|
|
|
## [2026-06-15] driver+test | Dingtian driver built; button blocker RESOLVED
|
|
Built the `dingtian` access driver (AccessControlDevice relay control + InputDevice
|
|
poll-based button events + new PreconditionDevice capability). Verified end to end on
|
|
real hardware (DT-R004 @ 10.0.10.172, HTTP config on :8080, UDP control :60001):
|
|
status read, relay pulse, input press/release. Disabled `input_link_relay` via the
|
|
driver's fixPreconditions (GET config → flag 0 + clear maps → POST config_set), then
|
|
confirmed: pressing inputs now fires NO relay (0000 status) — host-in-the-loop entry
|
|
works. The [[access-controller-button-flow]] blocker is RESOLVED. Gotcha recorded in
|
|
[[dingtian-relay]]: config_set requires injecting "command":"setconfig" after "status"
|
|
(GET omits it) or the write silently no-ops. Added httpPort config field (port 8080 ≠
|
|
default 80). Test script apps/server/scripts/dingtian-test.mjs. Next: input HTTP-push
|
|
endpoint + wiring input→ticket→pulseOpen.
|
|
|
|
## [2026-06-15] cleanup | Remove UHPPOTE/ZKTeco code; wiki → rejected/historical
|
|
Neither UHPPOTE nor ZKTeco is used (Dingtian chosen). Removed their code:
|
|
deleted access-uhppote.ts, uhppoted.d.ts, access.ts (zkteco/esp32 stubs), the
|
|
three uhppote-*.mjs scripts; dropped the `uhppoted` npm dep from both packages;
|
|
unregistered uhppote/zkteco/esp32-relay from the driver registry; updated example
|
|
comments. Catalog access drivers now = dingtian only. Build green.
|
|
Wiki: kept the pages but marked [[uhppote-controller]] + [[zkteco-controller]]
|
|
rejected/historical, [[uhppote-vs-esp32]] historical; re-pointed all "current
|
|
device" framing (standing-decisions, bom, overview, open-questions) to
|
|
[[dingtian-relay]]; noted no current driver uses [[device-discovery]]. Transferable
|
|
concepts (network-isolation, event-log-ingestion, barrier-not-a-door, threat-model)
|
|
kept as-is. Links lint clean; raw source untouched (immutable).
|
|
|
|
## [2026-06-15] feature | Dingtian input HTTP-push → backend (no polling)
|
|
Wired the device's "Input Link URL" feature so it HTTP-pushes button events to
|
|
our backend — no polling. Driver `configureInputPush()` writes input_link_url
|
|
(per-input server/port/path, en=1, active-LOW, plain HTTP) via the config API
|
|
(reusing the #writeConfig + command:setconfig helper). New backend route
|
|
`routes/devices.ts`: public `GET/POST /api/devices/dingtian/:deviceId/input/:n/{on,off}`
|
|
→ emits onto an internal device-events bus (device-events.ts, EventEmitter) for
|
|
the entry flow to consume. VERIFIED on hardware: configured device, real presses
|
|
on all 4 inputs pushed to the backend (input N on+off, source = device IP). Trust
|
|
model recorded in [[device-input-flow]]: flat network / no VLAN → backend is source
|
|
of truth, every open is a signed event (out-of-band open = anomaly); push endpoint
|
|
not behind cookie auth (machine call), shared-secret available as defence-in-depth.
|
|
Next: wire signed event + ticket print + pulseOpen.
|