Files
julian 5287be5278
Build & push images / images (push) Successful in 2m52s
CI / check (push) Successful in 43s
docs(wiki): catch-up sweep — five pages lagging the log
rongta-printer still named the cashino driver id (→ escpos + migration
0023 note); tariff-time-tiers listed the composer price preview as
deferred (→ delivered by the lab fee breakdown); tariff.md lab section
gained the breakdown + composer increment-guard paragraph; i18n.md now
records the "25 Qer 14:30" date standard + never-toLocaleString-for-
dates rule; fleet-deployment-komodo gained the park-lab stack + tier
table (the park-lab addition had also slipped the log — both fixed).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-07-07 13:08:55 +02:00

109 lines
7.4 KiB
Markdown
Raw Permalink 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-06-19
---
# 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).
## 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]].