Add device discovery (UHPPOTE LAN scan) to setup

UHPPOTE controllers self-announce via UDP broadcast, but the frontend had no way
to find them — the admin had to type the serial blind. Add a generic discovery
capability and surface it in the setup wizard.

packages/devices:
- DiscoverableDriver capability + DiscoveredDevice type + isDiscoverable() guard
  on the registry (optional, so any driver can opt in).
- uhppote driver implements discover() via uhppoted getDevices (UDP broadcast),
  mapping each controller's serial/IP/firmware into a DiscoveredDevice; extract
  shared buildCtx().

apps/server:
- GET /api/setup/discover/:driverId (admin-only): runs discover() and
  health-checks each found device so reachability shows before assigning.
- catalog now returns a `discoverable` driver-id list.

apps/web:
- SetupWizard "Scan for controllers" button for discoverable drivers; lists found
  devices with health badges; selecting one auto-fills serial + host. api client
  gains discoverDevices().

wiki: new device-discovery concept; cross-link from registry/setup/uhppote;
note the broadcast-permission (EACCES) deployment caveat; index + log.

Verified: catalog flags uhppote discoverable; discover runs and fails gracefully
without hardware; non-discoverable driver -> 400; missing token -> 401.
This commit is contained in:
2026-06-14 08:21:27 +02:00
parent 7438c0bdc2
commit a0e0fd9118
12 changed files with 320 additions and 39 deletions
+46
View File
@@ -0,0 +1,46 @@
---
type: concept
tags: [parking, architecture, devices, setup]
sources: [parking-system-architecture]
updated: 2026-06-15
---
# Device Discovery
An optional driver capability: **find devices on the LAN** so the admin doesn't have to type
connection details by hand during [[first-run-setup]]. Modeled generically so any driver can
opt in.
> Implementation-derived (from `@parking/devices` + setup API/UI), not the source doc.
## The capability
A driver may implement `DiscoverableDriver` — `discover() => DiscoveredDevice[]`. Each found
device carries an `id`, a `label`, a `config` blob to **auto-fill** the setup form, and `info`
(firmware, MAC, …). The [[device-registry]]'s `isDiscoverable()` guard lets the system treat it
as optional; the setup catalog returns a `discoverable` list of driver ids.
## UHPPOTE discovery
The [[uhppote-controller]] supports discovery natively: a **UDP broadcast** (`get-devices` on
`255.255.255.255:60000`) that **every controller on the LAN answers** with its serial, IP,
netmask, gateway, MAC, firmware version, and date. The official `uhppoted` lib exposes this as
`getDevices(ctx)`; the `uhppote` driver maps each result into a `DiscoveredDevice` (serial → id,
IP → host).
## Flow
1. The setup catalog flags `uhppote` as discoverable.
2. Admin clicks **Scan** → `GET /api/setup/discover/:driverId` (admin-only).
3. The server runs `discover()` and **health-checks each found device** so the admin sees
reachability before assigning.
4. Selecting a result **auto-fills serial + host**; the admin then assigns it to a lane.
## Deployment notes
- UHPPOTE discovery is a **broadcast** — the host socket needs broadcast permission (a raw
`send EACCES …:60000` means the OS blocked it). Works on the isolated device VLAN
([[network-isolation]]) where the controller and host share an L2 segment.
- Discovery shares the same unauthenticated UDP exposure as everything else UHPPOTE — another
reason the controllers live on an isolated VLAN ([[uhppote-udp-protocol]]).
- Cameras (Hikvision/Dahua via ONVIF/WS-Discovery) could implement the same interface later.
+2
View File
@@ -37,6 +37,8 @@ driver; **no business-logic change** — this is the [[device-adapter-pattern]]
(mirrors the "mixable per lane" principle — see [[trust-boundary]], [[entry-exit-readers]]).
- Config is **validated against the driver's declared fields** before persisting.
- Selections persist in the `lane_devices` table and drive runtime adapter construction.
- Drivers may optionally implement **[[device-discovery]]** (`discover()`), so the admin can scan
the LAN instead of typing connection details — UHPPOTE does this today.
Cameras are modelled as **snapshot-on-event**: the host requests an image at entry/exit; it's
stored and referenced from the signed event as an **independent record** — a fraud-control input
+2 -1
View File
@@ -16,7 +16,8 @@ each device's connection config.
## Flow
1. **Read the catalog** — `GET /api/setup/catalog` returns supported drivers per category (no
secrets, just schema). The web `SetupWizard` renders a picker + the driver's config fields.
secrets, just schema) plus a `discoverable` list. The web `SetupWizard` renders a picker + the
driver's config fields, and a **Scan** button for discoverable drivers ([[device-discovery]]).
2. **Assign per lane** — `POST /api/setup/assign` (admin-only, role-guarded; see
[[local-jwt-auth]]). The server validates the chosen driver + config against the registry
before persisting to the `lane_devices` table; unknown drivers / missing required fields are
+4 -2
View File
@@ -17,8 +17,10 @@ is UHPPOTE now → ZKTeco later (see [[bom]]). (See [[parking-system-architectur
> design needs: `openDoor`, `getStatus`, and the event-log set (`getEvent`, `getEventIndex`,
> `setEventIndex`, `recordSpecialEvents`) plus `setListener`/`listen` for auto-push — see
> [[event-log-ingestion]]. Transport defaults to **UDP** (broadcast `…:60000`), with optional
> per-call TCP on newer firmware. Note: the lib pulls one trivial extra dep (the npm `os`
> shim) and tends to use UDP broadcast, which needs socket broadcast permission on the host.
> per-call TCP on newer firmware. The driver also implements **[[device-discovery]]**
> (`getDevices` broadcast) so the setup wizard can scan for controllers. Note: the lib pulls one
> trivial extra dep (the npm `os` shim) and uses UDP broadcast, which needs socket broadcast
> permission on the host.
## What it is
+1
View File
@@ -52,6 +52,7 @@ Counts: 1 source · 14 entities · 10 concepts · 2 decision records.
- [[device-adapter-pattern]] — business logic talks to interfaces; swap hardware → new adapter.
- [[device-registry]] — catalog of selectable drivers per category (admin-configurable).
- [[first-run-setup]] — admin assigns devices per lane from the catalog at install.
- [[device-discovery]] — optional driver capability to scan the LAN (UHPPOTE UDP broadcast).
- [[barrier-not-a-door]] — never timed-close a barrier; safety lives in barrier firmware.
- [[trust-boundary]] — the core fork: network vs. device; auditable vs. unforgeable.
- [[fail-state-safety]] — entry fails closed, exit fails open; manual override; watchdog.
+12
View File
@@ -40,3 +40,15 @@ 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.