Files
parking_solution/packages/devices/src/drivers/reader.ts
T
julian 68d61f2d99 qr-reader: gee-qr-reader driver — assign in wizard, resolve lane by serial
The QR reader is a push device and the setup wizard assigns random-UUID ids, so
'id = serial' can't be set via the UI. Add a dedicated gee-qr-reader driver
(reader category) with a single 'serial' config field; the admin assigns it
normally and enters the device's serial (its cjihao).

The QR endpoint now resolves the lane by matching lane_devices.config.serial to
the scan's cjihao (instead of row id == cjihao), so no DB hand-editing. An
unassigned serial resolves to no lane -> status:0, gracefully.

Verified via inject through the real /api/setup/assign: assign {serial:
H05M2AFA} -> .jsp scan with a matching permit QR -> status:1 (accept) + open;
re-scan -> permit exit; unknown card -> status:0; unassigned serial -> status:0.
2026-06-16 12:36:58 +02:00

86 lines
3.3 KiB
TypeScript

import type { DeviceHealth, ReaderDevice, ReaderEvent } from "../interfaces.js";
import type { DeviceConfig, ReaderDriver } from "../registry.js";
import { hostField, portField, stubLog } from "./common.js";
// Reader drivers (RF / optical). Two integration paths: Wiegand reads reach the
// access controller directly (autonomous); TCP-IP readers are seen host-side.
// See wiki/concepts/entry-exit-readers.md. STUBS only.
class StubReader implements ReaderDevice {
#cb: ((r: ReaderEvent) => void) | null = null;
constructor(
readonly driverId: string,
protected readonly config: DeviceConfig,
) {}
async connect(): Promise<void> {
stubLog(this.driverId, "connect");
}
async disconnect(): Promise<void> {
stubLog(this.driverId, "disconnect");
}
async healthCheck(): Promise<DeviceHealth> {
return { status: "ready", detail: "stub" };
}
onRead(cb: (r: ReaderEvent) => void): void {
this.#cb = cb;
stubLog(this.driverId, "onRead handler registered");
}
/** Test hook for stubs — real drivers emit from hardware events. */
protected emit(r: ReaderEvent): void {
this.#cb?.(r);
}
}
export const wiegandReaderDriver: ReaderDriver = {
id: "wiegand-reader",
category: "reader",
label: "Wiegand reader (into controller)",
description:
"RF/optical reader wired Wiegand 26/34 into the access controller's reader port. Autonomous offline decisions.",
transports: ["wiegand"],
configFields: [
{ key: "door", label: "Controller reader port / door", type: "number", required: true, default: 1 },
{ key: "format", label: "Wiegand format", type: "select", required: true, default: "26", options: [
{ value: "26", label: "Wiegand 26" },
{ value: "34", label: "Wiegand 34" },
] },
],
create: (c) => new StubReader("wiegand-reader", c),
};
export const tcpipReaderDriver: ReaderDriver = {
id: "tcpip-reader",
category: "reader",
label: "TCP/IP reader (host-side)",
description:
"Network RF/optical reader seen only by the host; host decides and commands the relay.",
transports: ["tcp-ip"],
configFields: [hostField, portField(9000)],
create: (c) => new StubReader("tcpip-reader", c),
};
// GEE/Fondvision QR access reader (e.g. GEE-QR-ER80). A PUSH device: on each scan
// it HTTP-GETs our backend (/qa/mcardsea.<ext>) carrying its serial (cjihao); the
// backend resolves the lane by matching that serial to this device's `serial`
// config, decides, and replies the verdict (drives the beep). No host-side
// connection — the adapter is a stub; the real integration is the HTTP endpoint
// (apps/server routes/qr-reader.ts). See wiki/entities/gee-qr-er80.md.
export const geeQrReaderDriver: ReaderDriver = {
id: "gee-qr-reader",
category: "reader",
label: "GEE/Fondvision QR reader (HTTP push)",
description:
"QR/barcode access reader that HTTP-pushes each scan to the backend. Set its server IP/port to this host in the vendor tool; enter its serial here so scans resolve to this lane.",
transports: ["tcp-ip"],
configFields: [
{
key: "serial",
label: "Device serial (cjihao)",
type: "string",
required: true,
help: "The reader's serial as it reports in each scan (the `cjihao` field). Used to map scans to this lane.",
},
],
create: (c) => new StubReader("gee-qr-reader", c),
};