fix(reader): real ICMP liveness — QR reader status was a hardcoded "ready"
Two genuinely-offline QR readers showed GREEN: the adapter's healthCheck was
hardcoded to { ready, "stub" } and never probed. These are PUSH devices (scan →
GET our backend, resolve by serial) with NO TCP port, so a connect probe has
nothing to hit — the stub "solved" that by lying. False-healthy is the worst
failure for a status bar.
- Optional reader IP field (monitor-ONLY; scans still resolve by serial,
operation unchanged).
- Unprivileged ICMP ping (drivers/icmp.ts): shells /bin/ping -c1, exit-0 = reply.
No native dep, no CAP_NET_RAW. docker-compose.prod.yml sets
net.ipv4.ping_group_range so it works for the non-root container user.
- healthCheck: replies → ready, no reply → offline, NO IP → degraded
("set IP to monitor") — never a false green.
Verified on hardware: readers (10.0.10.7/.8) answer ICMP on the device VLAN;
UI Test connection → "● ready — ping 10.0.10.7". Tests: reader.test.ts (4).
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
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.
|
||||
@@ -18,8 +19,23 @@ class StubReader implements ReaderDevice {
|
||||
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> {
|
||||
return { status: "ready", detail: "stub" };
|
||||
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;
|
||||
@@ -80,6 +96,14 @@ export const geeQrReaderDriver: ReaderDriver = {
|
||||
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("gee-qr-reader", c),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user