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.
6.6 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | |||||
|---|---|---|---|---|---|---|---|---|---|
| entity |
|
|
2026-06-16 | open |
GEE-QR-ER80 (QR access reader)
The project's QR-code reader (GEE NFC LIMITED). A static optical scanner for QR /
DataMatrix / 1D barcode, optional ID/IC card. This is the ticket-encoding
scanner the design called for — read at the pay station and exit lane — and a path for QR
permit credentials. On hand: variant -Q-W (QR scanner; Wiegand/RS-232/RS-485).
(See gee-qr-er80 / raw/.)
What it is (and isn't)
- Optical, not RFID-prox. Earlier we assumed "ER80-EM" = a 125 kHz EM4100 card reader — the
datasheet corrects that: it's a QR/barcode scanner. The
-EMin the original label was a mis-id; the real model is GEE-QR-ER80. OptionalD/Cvariants add ID/IC card, but the unit on hand is QR-only (-Q). - Multi-interface (Wiegand 26/34, RS-232, RS-485, USB, TCP/IP); the
-Wvariant exposes Wiegand + RS-232/RS-485.
How it integrates — HTTP-GET push, server replies the verdict (confirmed via SDK)
The protocol is settled by the qrcode-sdk (not serial as first guessed).
The reader is configured (Windows tool QRCode_v1_6_5.exe) with a server IP/port and a "server
language" (only picks the URL path, e.g. /qa/mcardsea.php). On each scan it HTTP-GETs the host:
GET /qa/mcardsea.php?cardid=<QR>&mjihao=<devId>&cjihao=<devSN>&status=<2 chars>&time=<utc>
cardid = scanned data; status low digit = direction (1=in / 0=out). The host replies JSON
{data:[{cardid,cjihao,mjihao,status,time,output}],code:0} where reply status 1=valid (beep
2×) / 0=invalid (beep 1×), output 0=Access/1=WG26/2=WG34, time syncs the clock.
This is host-in-the-loop and SYNCHRONOUS: the GET is the access query and our reply is the
decision — it drives the reader's beep + output. So unlike a fire-and-forget reader, the endpoint
must decide (valid/invalid, direction from status) and reply, then also emit a DeviceReadEvent
on the read bus for the entry/exit/permit flows (parking-session, permit) to open the
barrier. (device-input-flow is the analogous push pattern; this one also returns a verdict.)
This explains the "no beep": feedback comes from the server's JSON reply, not locally. A non-JSON / missing reply ⇒ no beep even though the scan worked. So "no beep" ≠ "didn't scan."
- Pushes over plain HTTP to our
10.0.10.xhost (on the device subnet); no serial wiring, no Wiegand-decode hardware. Suits the host-in-the-loop model; autonomy is moot anyway (dingtian-relay has no onboard ACL). - Linux-supported, 4–15 VDC, default IP
192.168.1.99— fits the disk-os-hardening.
Resolved (2026-06-16)
- Protocol = HTTP GET poll + JSON verdict (above). The earlier "serial/Wiegand, find the baud"
open questions are moot — it's HTTP. Wiegand is the reader's output line on a valid read
(the reply
outputfield), not the host transport.
As-built (2026-06-16)
- Endpoint
GET/POST /qa/mcardsea.php(apps/server/src/routes/qr-reader.ts, public — the reader has no auth, sits on the device subnet). Parsescardid/mjihao/cjihao/status/time, runs the scan through the read dispatcher (permit match → permit flow; else transient exit), and replies the SDK verdict:status1=valid(beep 2×)/0=invalid(beep 1×),output0,time. - The read flows were refactored to return a
ReadOutcome{ accepted, direction, reason } so the endpoint's reply reflects the real accept/reject (the dispatcher decides AND opens the barrier via the flows). A fire-and-forget reader ignores the outcome. - Lane mapping: the endpoint keys the reader's
lane_devicesid off the device serial (cjihao) for now — so assign the reader withlane_devices.id = <serial>. Refine when the setup wizard models the reader's server-side identity properly. - Verified via inject: valid permit QR →
status:1+ open; re-scan → permit exit (still valid); unknown QR →status:0; reader on a barrier-less lane →status:0.
Verified on hardware (2026-06-16)
Captured a real scan (vendor-emulator logger on :3000). The reader does scan, send, and beep — the earlier "no beep" was simply that no server was answering on :3000 with valid JSON. Real GET:
GET /qa/mcardsea.jsp?cardid=52020056&mjihao=1&cjihao=H05M2AFA&status=11&time=1781634494
from 10.0.10.7 (referer: http://www.fondvision.com — the OEM is Fondvision)
- PATH carries the configured "server language" EXTENSION: this unit is set to JSP, so it
GETs
/qa/mcardsea.jsp— NOT.php. Our endpoint was registered at.phponly → it would have 404'd the real reader. Fixed: the route now registersphp/jsp/asp/aspx/cgi. cjihao=H05M2AFAis the device serial — the value our endpoint keys the lane on. So assign the reader withlane_devices.id = "H05M2AFA"(+ an access device on the same lane).mjihao= 1 (device id).cardid= the scanned barcode (52020056).status=11.- The reader beeped on the vendor reply with
status:0— so it acts on the reply;0= invalid/1-beep as documented. A matching permit/session will returnstatus:1→ 2-beep accept.
Assignment (as-built 2026-06-16)
A dedicated gee-qr-reader driver (device-registry, reader category) models the push reader:
its one config field is serial (the cjihao the device reports). The admin assigns it in the
first-run-setup like any device (normal UUID row id) and enters the serial. The QR
endpoint resolves the lane by matching config.serial to the scan's cjihao — not by row id —
so no DB hand-editing. Set the reader's server IP/port to this host in the vendor tool; assign +
enter its serial here.
- Verified via inject: assign
gee-qr-reader{serial:"H05M2AFA"} on a lane w/ an access device → a.jspscan with that serial + a matching permit QR →status:1(2-beep accept) + open; re-scan → permit exit; unknown card →status:0; unassigned serial →status:0(no lane, graceful). - Note
tcpip-readeris the WRONG model for this device (host-connects-out, a stub) — usegee-qr-reader.
Open / next
- Re-test on hardware against the real app (now
.jsp-aware + serial-resolved): scan → expect astatus:12-beep when the QR matches a permit/open session. outputis replied as0(Access). Confirm on hardware whether the reader needs1/2(WG26/34) to drive its access line, vs.0.