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
7.4 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | |||||
|---|---|---|---|---|---|---|---|---|---|
| concept |
|
2026-06-19 | open |
Ticket Encoding & Scanning
How a transient parking-session's ticket id is printed, carried by the customer, and read back at the pay station and exit. This is the physical backbone of the transient flow — the thing that links entry → pay → exit when there's no plate.
The ticket id is the session key
At entry the system mints a vehicle_entry event with a ticket id (identity) and prints a
ticket the customer keeps. That same id is read back later to find the session. Properties the id
must have:
- Opaque + unguessable — a random id (not a sequential count an attacker could iterate to claim someone else's cheaper session). Sequential physical stock numbering is a separate reconciliation aid (reconciliation pre-numbered stock), not the scan key.
- All-numeric (as-built 2026-06-17) — so ANY legacy 1D barcode scanner reads it and an operator
can hand-key it. Random (not sequential), so "all-numeric" does not weaken the unguessable
property. Format (shortened 13→11 on 2026-06-19): 11 digits = 10 cryptographically-random
digits + 1 Luhn check digit. Length is driven by guess-resistance, not volume — with 10^10
valid ids and the Luhn digit rejecting 9/10 malformed guesses, a blind attempt at a currently-OPEN
ticket lands at ~1-in-10^7 even with thousands parked (the threat-model threat),
which is the property that matters; raw count (a billion) was never the constraint. Collisions stay
negligible at lot scale.
newTicketId()inapps/server/src/entry-flow.ts; validate withvalidateTicketCode()— now length-agnostic (\d{10,14}+ Luhn) so legacy 13-digit tickets in circulation keep validating. Gate MANUAL entry only — a scanned/looked-up id already in the ledger is authoritative regardless of format.Why 11, not the requested 9 (2026-06-19): 9 digits (10^8 space) against ~1000 live tickets gives ~1-in-10^5 per blind guess — an operator scripting guesses could find a valid open ticket in minutes. 11 keeps a strong anti-forgery margin AND was the actual fix for a printer overflow (below). The shorter id is why the width-3 barcode now fits the 72mm Cashino line.
- Format is a property of minting, not the schema —
identity/sessions.idare free-formtext, so changing the id format is a code change with no migration. LegacyT-<uuid>ids (pre-2026-06-17) remain valid keys and coexist with numeric ones. - Single logical session — scanning it at the pay station finds the open session; after payment it's the proof-of-paid the exit checks.
Encoding: Code128 numeric barcode — printed by the booth dispenser
- The rongta-printer prints the ticket id as a 1D Code128 barcode (the all-numeric code), with the same code in large human-readable digits below it, then the entry time. Code128 over QR for the primary symbology because the booth's reader hardware is unknown and a legacy 1D laser scanner is the lowest common denominator — and the printed digits mean total reader failure still leaves a hand-keyable code. A QR for phone/imager scanning may be added later behind an admin toggle (deferred — see Open).
As-built (2026-06-17).
renderTicket()in rongta-printer (packages/devices/src/drivers/printer-rongta.ts) emits the Code128 via ESC/POSGS k(code set B) — rendered by the printer firmware, so there is no image-rendering step and no new dependency (keeps the MIT/Apache/BSD constraint). Resilience rationale: the booth's reader is uncertain, so the id is carried in two independently-readable forms (1D barcode / printed digits). The "operator scans with a phone" path reuses the existing dispatch flow (entry-exit-readers) and is tracked separately (not yet built).
Barcode GEOMETRY must fit the paper width — root cause of a real garbage-print incident (2026-06-19). The Cashino rongta-printer entry dispenser printed tickets as raster garbage (solid black bars / banding) while the rongta-printer printed the identical byte stream fine. Not data corruption: the Code128 symbol overflowed the print line. Math: a Code128-B symbol is
(11·chars + 35) · moduleWidthdots. At 203 dpi the KP-300H prints 72mm = 512 usable dots (the Rongta runs 80mm = 576). The old 13-digit id atGS w 3= ~534 dots overran 72mm, and the firmware rendered the overflow as pixels; the Rongta's 80mm had just enough room — which is why only the Cashino failed. The 11-digit id at width 3 = ~468 dots, fits both widths, and scanned the full value at the exit reader (verified on hardware). Lessons: (1) keep(11·len+35)·moduleWidthunder the narrowest deployed printer's usable dots; (2) a too-NARROW module (GS w 2) is also bad — it scanned but returned truncated values (partial reads logged asexit.refused.noSessionanomalies). Width 3 + 11 digits is the verified sweet spot.code128()inpackages/devices/src/drivers/printer-escpos.ts.
- Scan points (both host-side reads — entry-exit-readers):
- Pay station — customer scans the ticket → host finds the session → shows fee → takes
payment (tariff, pay-on-foot) → appends
payment. - Exit lane — customer scans the (now paid) ticket → host validates paid + within
gracePeriodExit→vehicle_exit→pulseOpen.
- Pay station — customer scans the ticket → host finds the session → shows fee → takes
payment (tariff, pay-on-foot) → appends
- The scanner is a device behind an adapter (device-adapter-pattern): a new
ReaderDevicekind (QR/barcode imager) — likely the sameIdentitySource = "ticket"/"qr"path. Keeps the app device-agnostic; hardware model is procurement (bom, open-questions).- On hand: the dingtian-dt008-reader QR/RFID reader (Wiegand/TCP-IP/USB/RS485,
Linux-supported) — the concrete scanner for this path. A serial
ReaderDeviceadapter feeds thereadbus; via HTTP-GET push (see dingtian-dt008-reader).
- On hand: the dingtian-dt008-reader QR/RFID reader (Wiegand/TCP-IP/USB/RS485,
Linux-supported) — the concrete scanner for this path. A serial
Ticketless alternative (plate as the ticket)
Where the opencv-anpr-service/LPR captures the plate, the plate can be the session key instead of a printed ticket — drive in, plate read, drive to pay station and enter plate (or it's looked up), pay, exit by plate. No paper. The two can coexist per lane (entry-exit-readers "both share a relay"); a printed QR ticket is the fallback when a plate isn't captured or is low-confidence (recognition is advisory — opencv-anpr-service).
Open
- Primary symbology decided: Code128 set B over the all-numeric id (as-built above). Still open: what else prints (site name, tariff summary, help number).
- Optional QR (deferred) — an admin toggle to ALSO print a QR for phone/imager users. The
code128()/qrCode()ESC/POS helpers were prototyped 2026-06-16; QR was dropped 2026-06-17 in favor of "1D barcode + hand-keyable numeric code" because the booth's reader hardware is unknown. Revisit when mobile scanning is wanted. - Phone-scan fallback (operator scans a ticket with a phone when a reader is down) — designed but not built: an authenticated route feeding the same dispatcher + a minimal mobile scan UI.
- Scanner hardware (imager model; same unit at pay station and exit?).
- Lost/damaged ticket → the lost-ticket path (parking-session, tariff admin-arbitrary amount).