f6e35bbebf
An early wrong assumption named the QR/RFID access reader "GEE" / "GEE/Fondvision" / "GEE-QR-ER80" (and summarized a raw GEE PDF as its datasheet). There is no GEE device — it's the Dingtian DT-008 (dingtian-tech.com/en_us/qr_code_reader.html), the same vendor as the relay board, which is why it integrates the identical HTTP-GET-push way. Code: - Driver symbol geeQrReaderDriver → dingtianQrReaderDriver; label → "Dingtian DT-008 QR/RFID reader (HTTP push)"; comments/description rewritten to the real DT-008 facts (Wiegand 26/34, TCP/IP, USB, RS485 — not RS-232; QR/barcode + ID/IC/NFC — not DataMatrix/1D). - Persisted driverId "gee-qr-reader" → "dingtian-qr-reader" (the registry lookup key + the row created on assign in qr-reader.ts). - Migration 0015 rewrites existing devices.driver_id rows so configured readers keep resolving (applied to the dev DB — 2 rows; the booth applies it on boot). Behaviour is unchanged: naming + the persisted id only. Wiki + memory: - Renamed entities/gee-qr-er80.md → dingtian-dt008-reader.md and sources/gee-qr-er80.md → dingtian-dt008.md; rewrote both to the real DT-008 product-page specs while KEEPING all the verified-on-hardware protocol facts (cjihao serial, .jsp path, Connection: close). Fixed every cross-reference + "GEE" mention in 6 other pages. Memory gee-reader-serial-binding → dingtian-reader-serial-binding. The only surviving "GEE" mentions are deliberate naming-correction notes, the raw PDF filename, and the append-only log history. Full workspace build/lint/test green; dev DB readers verified resolving to the registered dingtian-qr-reader driver. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
111 lines
4.7 KiB
TypeScript
111 lines
4.7 KiB
TypeScript
import type { DeviceHealth, ReaderDevice, ReaderEvent } from "../interfaces.js";
|
|
import type { DeviceConfig, ReaderDriver } from "../registry.js";
|
|
import { hostField, portField, stubLog } from "./common.js";
|
|
import { icmpPing } from "./icmp.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");
|
|
}
|
|
/**
|
|
* Liveness. These readers PUSH (scan → GET our backend) and expose no TCP port, so
|
|
* there's nothing to connect-probe. If the admin gave the reader's IP we ICMP-ping
|
|
* it (powered + on-network); a reply → ready, no reply → offline. With NO IP we
|
|
* report `degraded` ("set IP to monitor") rather than a false `ready` — a push
|
|
* device that's silent is indistinguishable from a dead one, so claiming `ready`
|
|
* unconditionally (the old behaviour) hid offline readers behind a green dot.
|
|
*/
|
|
async healthCheck(): Promise<DeviceHealth> {
|
|
const host = this.config.host ? String(this.config.host) : "";
|
|
if (!host) {
|
|
return { status: "degraded", detail: "push device — set IP to monitor" };
|
|
}
|
|
const alive = await icmpPing(host);
|
|
return alive
|
|
? { status: "ready", detail: `ping ${host}` }
|
|
: { status: "offline", detail: `no ping reply from ${host}` };
|
|
}
|
|
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),
|
|
};
|
|
|
|
// Dingtian DT-008 QR/RFID access reader (same vendor as the relay board). Scans QR/barcode
|
|
// + ID/IC/NFC cards. 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). Interfaces: Wiegand 26/34, TCP/IP, USB, RS485. See
|
|
// wiki/entities/dingtian-dt008-reader.md.
|
|
export const dingtianQrReaderDriver: ReaderDriver = {
|
|
id: "dingtian-qr-reader",
|
|
category: "reader",
|
|
label: "Dingtian DT-008 QR/RFID reader (HTTP push)",
|
|
description:
|
|
"Dingtian DT-008 QR/barcode/RFID 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.",
|
|
},
|
|
{
|
|
// OPTIONAL: the reader pushes by serial (operation needs no IP), but giving its
|
|
// IP lets the status monitor ICMP-ping it for a real online/offline dot instead
|
|
// of an always-green stub. Leave blank to skip monitoring (shows "set IP").
|
|
...hostField,
|
|
required: false,
|
|
help: "Optional: the reader's IP, used ONLY to monitor it (ping). Scans still resolve by serial. Leave blank to skip liveness monitoring.",
|
|
},
|
|
],
|
|
create: (c) => new StubReader("dingtian-qr-reader", c),
|
|
};
|