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,44 @@
|
||||
// Guarded relay (door-open) test for the UHPPOTE controller.
|
||||
//
|
||||
// Prompts before firing each relay so a door only opens when you're ready and
|
||||
// watching. This is a 2-door controller, so it tests doors 1 and 2 by default.
|
||||
//
|
||||
// node apps/server/scripts/uhppote-relay.mjs # doors 1,2 (prompted)
|
||||
// node apps/server/scripts/uhppote-relay.mjs 1 # only door 1
|
||||
// YES=1 node apps/server/scripts/uhppote-relay.mjs # no prompts (fires!)
|
||||
//
|
||||
// SAFETY: openDoor only expresses INTENT to open. The controller / barrier
|
||||
// operator owns the close timing and anti-crush — we never time a close.
|
||||
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import { stdin, stdout } from "node:process";
|
||||
import { controller, makeCtx, uhppoted } from "./uhppote-common.mjs";
|
||||
|
||||
const ctx = makeCtx();
|
||||
const doors = process.argv.slice(2).map(Number).filter((n) => n >= 1 && n <= 4);
|
||||
const targets = doors.length ? doors : [1, 2];
|
||||
const autoYes = process.env.YES === "1";
|
||||
|
||||
console.log(`controller : ${controller.id} @ ${controller.address}`);
|
||||
console.log(`testing doors: ${targets.join(", ")}${autoYes ? " (auto, no prompts)" : ""}\n`);
|
||||
|
||||
const rl = autoYes ? null : createInterface({ input: stdin, output: stdout });
|
||||
|
||||
for (const door of targets) {
|
||||
if (rl) {
|
||||
const ans = await rl.question(`Open door ${door}? [y/N] `);
|
||||
if (ans.trim().toLowerCase() !== "y") {
|
||||
console.log(` skipped door ${door}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const res = await uhppoted.openDoor(ctx, controller, door);
|
||||
console.log(` door ${door}: openDoor -> ${JSON.stringify(res)}`);
|
||||
} catch (e) {
|
||||
console.log(` door ${door}: ERROR ${e.code ?? e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
rl?.close();
|
||||
console.log("\ndone.");
|
||||
Reference in New Issue
Block a user