Files
parking_solution/packages/devices/src/drivers/reader.ts
T
julian f6e35bbebf
Build desktop / desktop (push) Successful in 4m16s
Build & push images / images (push) Successful in 2m46s
CI / check (push) Successful in 38s
fix(reader): correct the QR reader's identity — Dingtian DT-008, not "GEE"
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
2026-06-28 17:39:15 +02:00

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),
};