Two independent wiki updates bundled (all docs):
1. dingtian-dt008-reader.md: phantom optical decodes on the park-buzi EXIT
reader (empty pre-opening site, low-sun afternoons). Chain of evidence:
READ log lines carry the reader's own serial (H05MA5B0) → physical device,
not a network source; snapshot shows nobody present; code shapes are the
giveaway (6-digit numerics = checksum-less Interleaved 2-of-5, lone "C" =
Code39/Codabar artifact) → 1D engine decoding sun-made stripe patterns
(striped arm, fence shadows, glare). No fraud exposure (11-digit Luhn ids
can't match); noise only. Fix on the entity page: vendor-tool symbology cut
to QR+Code128 + min decode length, BOTH readers; config lives ON the device
→ re-apply after any factory reset/swap. Deliberately NOT filtering
impossible codes server-side — probe recording is the anomaly path's job.
2. Backfilled two shipped-but-undocumented features (six code files already
linked the first page as if it existed):
- concepts/entry-presence-bypass.md — admin drops a FAULTY presence signal
(granular radar/camera by decision, not a master switch); every flip is a
signed config_change; persists till off; tickets stamped presenceBypassed;
radar-bypass cooldown tradeoff; "the admin is not the adversary, but
trusted never means invisible".
- concepts/setup-relay-test.md — admin-only commissioning pulse, signed
barrier_open_command BEFORE the fire so a test open never reads as the
out-of-band-open fraud signal; saved controllers/declared relays only;
radarAlert lamps excluded; pulseOpen only.
Cross-linked from operator-issued-entry.md, cataloged in index.md, logged.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
9.9 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| entity |
|
|
2026-07-04 | open |
Dingtian DT-008 (QR/RFID access reader)
The project's QR / barcode / RFID reader — Dingtian DT-008, the same vendor as the
dingtian-relay (which is why it integrates the same HTTP-push way). A static
optical + card scanner for QR / barcode (simple) and ID/IC/NFC cards. This is the
ticket-encoding scanner (read at the pay station / exit lane) and a path for QR
or RFID subscription credentials. Product page: dingtian-tech.com/en_us/qr_code_reader.html.
(See dingtian-dt008 / raw/.)
Naming correction (2026-06-28). Through most of this project this reader was wrongly called "GEE" / "GEE/Fondvision" / "GEE-QR-ER80" — a bad early assumption. There is no GEE device; it is the Dingtian DT-008. The driver id was renamed
gee-qr-reader→dingtian-qr-reader(migration 0015 rewrites existing rows). All protocol/behaviour facts below were observed on the real hardware and remain correct — only the device identity was wrong.
What it is
- QR/barcode + card. Reads QR / simple barcode AND ID/IC/NFC cards (~0.2 s, 0–10 cm). (An earlier note guessed "ER80-EM = 125 kHz EM4100 prox" — that was part of the same mis-id.)
- Interfaces: Wiegand 26/34, TCP/IP, USB, RS485. (NOT RS-232 — that was a fictional-datasheet claim.) For our integration it talks HTTP over TCP/IP; Wiegand is the reader's output line on a valid read, not the host transport.
- Power 9–16 V DC, 800 mA; 86×86×42 mm; −30…70 °C. Fits the disk-os-hardening.
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, subscription) 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). - Default IP
192.168.1.99(set its server IP/port to this host in the vendor tool).
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. - Reader identity: the endpoint matches the device serial (
cjihao) against each reader'sconfig.serialto find itsdevicesrow; the dispatcher then resolves the relay that row is bound to (config.controllerId+relay) and opens it. See entry-exit-points. - 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 matches against the reader'sconfig.serial. So assign the reader withconfig.serial = "H05M2AFA"and bind it to a controller relay.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 dingtian-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), enters the serial, and binds
it to a controller relay. The QR endpoint resolves the reader 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 + bind it here.
- Verified via inject: assign
dingtian-qr-reader{serial:"H05M2AFA"} bound to an access relay → 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 relay, graceful). - Note
tcpip-readeris the WRONG model for this device (host-connects-out, a stub) — usedingtian-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.
⚠️ Phantom optical decodes from sunlight patterns (park-buzi, 2026-07-04)
With the site EMPTY (pre-opening, verified live + by snapshot), the exit reader
(cjihao=H05MA5B0) pushed spontaneous scans at random afternoon times (observed 16:38–18:14,
low-western-sun hours): cardid values like 997492, 389861, 024358, 192793 — and once a
lone C. Server logs (READ serial= lines) confirm the pushes carry the reader's own serial
and resolve to its assigned row, so this is the physical reader decoding, not a network source.
Diagnosis: the scan engine ships with many 1D symbologies enabled, some with weak/no checksums.
Six-digit all-numeric strings are the signature of Interleaved 2-of-5 (even-length, digits-only,
no checksum — any high-contrast stripe pattern of the right proportions "decodes"); a lone C is a
Code39/Codabar artifact (Codabar start/stop chars are A–D). Low sun creates exactly such
patterns at a gate: the striped barrier arm, fence/railing shadows sweeping as the sun moves, glare
bands. RFID noise would instead give repeating UID-shaped values.
Impact: noise, not risk. Every phantom was REFUSED fail-closed (exit.refused.noSession, a
signed anomaly — #52–58 in the feed); a phantom can never match a ticket (ticket-encoding ids
are 11-digit + Luhn, so a 6-digit read has nothing to match). Do NOT filter "impossible" codes
server-side — recording every probe of an exit reader is what the anomaly path is for; fix at the
source instead:
Fix (vendor tool, per reader — config lives ON THE DEVICE, not in our DB): disable every symbology except QR + Code128 (all our credentials); if offered, set minimum decode length ≥ 10 and require checksums. Apply to BOTH readers. ⚠️ A factory reset or a swapped unit silently re-enables the phantom symbologies — re-apply after any reset/replacement. Physical fallback if any noise survives: hood/visor the window, tilt it down, avoid facing the striped arm.
⚠️ Reply MUST set Connection: close (verified on hardware)
The reader sends Connection: keep-alive but only acts on the verdict (beep/output) once the TCP
socket CLOSES. Fastify's default keeps the connection alive → the reader waits out a ~10 s
keep-alive timeout before beeping, even though the server replied in ~15 ms. Every vendor demo
replies Connection: close and shuts the socket. Fix: the endpoint sets
reply.header("connection","close"). Symptom if regressed: correct accept/reject but a ~10 s lag
before the beep. (The request arrives fast; the delay is entirely the reader waiting for close.)