Files
parking_solution/wiki/entities/rongta-printer.md
T
julian 552d87d75b feat(devices): K200L printer driver — live cover/paper status from the J-Speed LAN board
The park-buzi printer is a K200L (Xprinter/ICS XP-K200L; its LAN board and USB
descriptor call it "POS-80"). Its board serves the Rongta's five-row status table
under /prt_status.htm — but the reply carries no HTTP status line or headers, which
node:http rejects, so the Rongta driver could never read it and the unit was filed
in July as "no status page → generic driver" (reachability only).

New `k200l` driver (printer-k200l.ts): prints through the generic ESC/POS device
(same bytes, TCP 9100 or usblp) and reads the page over a raw socket, tolerant of
both the headerless and a proper HTTP reply. Mapping mirrors the Rongta: board
unreachable → offline; page not understood → degraded, never ready; any fault →
degraded naming it; USB → reachability floor. The Rongta driver is untouched.
Tests replay the captured headerless page (devices suite 76). Live against the
lab unit: ready; with the cover open the board reports cover open, paper out,
off-line.

Wiki: new k200l-printer entity (names, network setup from factory
192.168.123.100, board quirks, status page, what it means for park-buzi — over
USB the app never saw cover/paper state at all), cross-links on the Rongta,
status-monitoring, USB-transport and WSL-networking pages (parking-net pinned to
eth1 while the LAN NIC is eth0), index.

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
2026-09-09 12:34:59 +02:00

113 lines
7.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: entity
tags: [parking, hardware, printer, device]
sources: []
updated: 2026-09-09
---
# Rongta 80mm thermal printer
The chosen ticket/receipt printer: a **Rongta RP-series 80mm network thermal printer** (and the
many ESC/POS-compatible OEM clones that share its firmware). Driver `rongta` in
`packages/devices` implements [[device-adapter-pattern|PrinterDevice]].
## Transport & protocol
- **ESC/POS over a raw TCP socket on port 9100** (the JetDirect/RAW convention). The driver
opens the socket, writes the ESC/POS byte stream, and **closes GRACEFULLY**.
> **Graceful close — fixed 2026-06-19.** `sendRaw()` previously did `write(payload)` then
> `destroy()` on the write callback. But a `Socket.write()` callback fires when bytes reach the
> *local kernel buffer*, NOT when the peer has read them — so `destroy()` could send a TCP **RST**
> that truncates the job mid-stream, leaving the printer a desynced ESC/POS stream (raster garbage).
> Now it `end(payload)` (write + FIN, a clean half-close) and resolves on the socket's `close`
> event — which only fires after the printer has drained the bytes. A timeout *after* the write
> completed is treated as success (some printers never send their own FIN), so a delivered job is
> never spuriously failed. A latent bug found while diagnosing the KP-300H garbage; it was NOT the
> cause of that (see overflow gotcha below) but is a real truncation risk on its own.
- **No authentication** on the print socket — anyone who can reach port 9100 can print. Like
every other field device it must sit on the **isolated device VLAN** ([[network-isolation]]).
There is no real HTTP/control boundary on the device (same posture as [[dingtian-relay]]).
- **Health check** is a TCP connect probe to 9100. The print socket exposes no status protocol
we rely on; the print itself is the real reachability test (failover attempts the print).
- **Live status** comes from the device's own web page `http://<host>/prn_stat.htm` (port 80),
which decodes Cover Open / Cutter Error / Paper End / Paper Near End / Off-Line into Yes/No.
We scrape that rather than hand-decode `DLE EOT` — this clone's DLE EOT reply bytes do **not**
match the canonical ESC/POS bit layout (verified on hardware), so trusting the device's own
decode avoids a false-healthy. Implemented as `readStatus()`; see [[printer-status-monitoring]].
- **USB transport (added 2026-06-24).** The same driver can instead drive a printer over a local
USB `usblp` char device (`/dev/usb/lp0`) — `config.transport` (`tcp-ip` | `usb`) picks the wire
behind one render layer (the ESC/POS bytes are identical). The status web page is a **network**
feature, so a **USB Rongta degrades to reachability-only** monitoring (open-the-node probe, no
paper/cover verdict — the same honesty floor as the Cashino). Driving USB depends on the appliance
image (`usblp` bound + a udev write-access rule) — a provisioning step, open-questions #14. Full
rationale in [[printer-usb-transport]].
## Deployment (this site)
- First printer verified reachable at **10.0.10.6:9100** from the host (TCP connect OK,
2026-06-14).
- **Two printers configured (as of 2026-06-18)**, by role — see [[printer-roles-failover]]:
- **entry-dispenser** — `10.0.10.9`, outside at the lane; the driver takes the entry
ticket. This unit is a **Cashino**, not a Rongta: it prints the same ESC/POS stream but
serves no `/prn_stat.htm` status page, so it runs on the generic ESC/POS driver
(id `escpos` — renamed from `cashino` 2026-07-06, migration 0023; reachability PING
only, no paper/cover status) — see [[printer-status-monitoring]], [[printer-usb-transport]].
- **booth-receipt** — `10.0.10.10`, inside the booth; receipts, AND the backup that prints
the entry ticket if the outside dispenser is offline. This unit is a **Rongta** (`rongta`
driver, full status-page monitoring).
- **Not a Rongta, its own driver since 2026-09-09:** the **K200L** (Xprinter/ICS XP-K200L family;
LAN board calls itself "POS-80") — the park-buzi unit and the lab's 10.0.10.11 unit. Same
five-row status table under **`/prt_status.htm`** (not `/prn_stat.htm`), served without HTTP
headers, so it has the `k200l` driver with a raw-socket fetch. See [[k200l-printer]].
## Ticket rendering
`printTicket(TicketData)` builds ESC/POS: `ESC @` init, centered/bold/double-size header,
lane, ticket id, issued-at, feed + partial cut (`GS V B`). CP437/ASCII subset. The entry ticket
encodes the id as a **1D Code128** barcode (`GS k`).
**`printSubscriptionCard(SubscriptionCardData)`** (added 2026-06-18) renders a **2D QR** of the
[[subscription]] code via ESC/POS **`GS ( k`** (model 2, EC level M) — firmware-rendered, no bitmap
dependency — plus the code as text + holder/validity. Used for the auto-printed + reprintable
subscription card. (Verified: the `GS ( k` store/print byte sequences + the embedded code appear on
the wire against a TCP capture.)
**`printReceipt(ReceiptData)`** (added 2026-06-18) — the payment receipt / exit voucher
([[booth-exit-flow]]). Same ESC/POS preamble; figures + amount, optional Code128 + grace.
### CP852 codepage — two gotchas found on a real misprint (2026-06-18)
The driver selects **CP852** (Latin-2) via `ESC t 18` and maps the Albanian letters to their CP852
bytes. A printed voucher surfaced two encoding bugs, both now fixed in `printer-escpos.ts`:
- **Uppercase `Ë` was mapped to `0xEB` — wrong.** In CP852 `0xEB` is `ű`; the correct byte for
`Ë` (U+00CB) is **`0xD3`**. The title `BILETË DALJE` / `FATURË PAGESE` printed garbled until fixed.
(Lowercase `ë` = `0x89` was always correct.) Anyone extending the CP852 map must verify bytes
against the actual Unicode.org `CP852.TXT`, not guess.
- **`Intl.NumberFormat` injects a NO-BREAK SPACE.** Formatting `ALL` money yields `"1000 Lekë"`
(separator = U+00A0, sometimes U+202F narrow NBSP) — neither is in CP852, so it printed as `1000?Lekë`.
`line()` now normalises U+00A0/U+202F → a plain space before encoding. Any Intl-formatted value on a
ticket is affected, not just money.
### KP-300H barcode overflow — only the Cashino garbled (2026-06-19)
The Cashino entry dispenser printed **raster garbage** on entry tickets while the booth Rongta printed
the *same* byte stream cleanly. Root cause was **barcode line-overflow, not corruption**: a 13-digit
Code128 at module width 3 (~534 dots) overran the KP-300H's **72mm = 512-dot** line; the Rongta's
80mm (576) had room. Plain-text-only prints were clean, isolating it to the `GS k` barcode. **Fix:
shorten the ticket id 13→11 digits** (~468 dots — fits 72mm) — see [[ticket-encoding]] for the
geometry. Module width must stay 3: a test at width 2 scanned but returned *truncated* values.
**General rule:** size firmware barcodes/QRs to the **narrowest** deployed printer's usable dot width.
### Human dates on printed slips — Albanian (2026-06-19)
`stamp()` now formats timestamps as **`19 Qershor 2026 10:48:25`** (Albanian month, 24h with seconds)
instead of `YYYY-MM-DD HH:MM`. Month names are a hardcoded `SQ_MONTHS` table (the appliance browser's
ICU lacks Albanian locale data — see [[i18n]]). `stamp` is exported as **`formatStampSq`** so the
shift [[shift|Z-report]] shares the one Albanian date format. Applies to ticket issue time, receipt
entry/paid rows, and subscription validity dates (date-only). Consistent with the
"[[i18n|printed paper is always Albanian]]" rule.
## Status
Driver written and compiles; entry-ticket layout is a first pass; live status monitoring is
implemented and verified ([[printer-status-monitoring]]). The receipt/exit layout and the
cash-drawer kick (ESC/POS `ESC p`) are **not yet implemented** — they arrive with the
exit/payment flow. Replaces the generic "Epson TM / Citizen" booth-printer line in [[bom]].