Files
parking_solution/apps/server/scripts/uhppote-common.mjs
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

73 lines
2.4 KiB
JavaScript

// Shared helpers for the UHPPOTE hardware test scripts.
// Run directly against the device (independent of the HTTP server).
//
// node apps/server/scripts/uhppote-listen.mjs
// node apps/server/scripts/uhppote-relay.mjs
//
// Env overrides:
// UHPPOTE_SERIAL controller serial (default 225088491)
// UHPPOTE_HOST controller IP (default 10.0.10.3)
// UHPPOTE_BCAST Config broadcast (default derived from HOST subnet)
// HOST_IP this host's IP the controller pushes events to
// (default: auto-detected interface on the controller's subnet)
import { networkInterfaces } from "node:os";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const uhppoted = require("uhppoted");
export const SERIAL = Number(process.env.UHPPOTE_SERIAL ?? 225088491);
export const HOST = process.env.UHPPOTE_HOST ?? "10.0.10.3";
/** Subnet-directed broadcast for the interface that owns `ip`. */
function broadcastForHost(ip) {
const o = ip.split(".").map(Number);
for (const ifaces of Object.values(networkInterfaces())) {
for (const i of ifaces ?? []) {
if (i.family !== "IPv4" || i.internal) continue;
const a = i.address.split(".").map(Number);
const m = i.netmask.split(".").map(Number);
if (o.every((x, k) => (x & m[k]) === (a[k] & m[k]))) {
return a.map((x, k) => (x & m[k]) | (~m[k] & 0xff)).join(".");
}
}
}
return "255.255.255.255";
}
/** This host's own IP on the controller's subnet (where it should push events). */
export function hostIpOnControllerSubnet(ip = HOST) {
if (process.env.HOST_IP) return process.env.HOST_IP;
const o = ip.split(".").map(Number);
for (const ifaces of Object.values(networkInterfaces())) {
for (const i of ifaces ?? []) {
if (i.family !== "IPv4" || i.internal) continue;
const a = i.address.split(".").map(Number);
const m = i.netmask.split(".").map(Number);
if (o.every((x, k) => (x & m[k]) === (a[k] & m[k]))) return i.address;
}
}
return null;
}
export const BCAST = process.env.UHPPOTE_BCAST ?? broadcastForHost(HOST);
export function makeCtx(timeoutMs = 5000) {
return {
config: new uhppoted.Config(
"parking",
"0.0.0.0",
`${BCAST}:60000`,
"0.0.0.0:60001",
timeoutMs,
[],
false,
),
locale: "en-US",
};
}
export const controller = { id: SERIAL, address: HOST, protocol: "udp" };
export { uhppoted };