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
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
type: concept
|
||||
tags: [parking, printer, device, monitoring, reliability]
|
||||
sources: []
|
||||
updated: 2026-06-14
|
||||
updated: 2026-09-09
|
||||
---
|
||||
|
||||
# Printer status monitoring
|
||||
@@ -90,3 +90,17 @@ reads. Full repo typechecks.
|
||||
Yes/No and degrade on anything unexpected, so this is a confidence check, not a blocker.
|
||||
- Tying a `degraded`/`offline` entry-dispenser into [[printer-roles-failover]] failover and the
|
||||
(not-yet-built) entry flow's all-printers-down policy ([[device-input-flow]]).
|
||||
|
||||
## K200L — a second status-page board, and a fetch that node:http can't do (2026-09-09)
|
||||
|
||||
The park-buzi printer turned out to be a **K200L** (Xprinter/ICS XP-K200L; LAN board "J-Speed
|
||||
Ethernet WebConfig 1.02", self-named "POS-80"). Its board serves the **same five decoded Yes/No
|
||||
rows** as the Rongta page — under **`/prt_status.htm`**. Two things kept it invisible until now:
|
||||
the Rongta driver only knew `/prn_stat.htm` (so in July the unit was filed as "no status page →
|
||||
generic driver"), and the board's reply carries **no HTTP status line or headers** (HTTP/0.9
|
||||
style), which `node:http` rejects outright and `curl` shows as an empty `000`. The new **`k200l`**
|
||||
driver (`printer-k200l.ts`) prints through the generic ESC/POS path and reads the page over a raw
|
||||
TCP socket, tolerant of both reply shapes; mapping is the Rongta one (unreachable → offline; page
|
||||
not understood → degraded, never ready; any fault → degraded naming it; else ready; USB →
|
||||
reachability floor). Bench-verified: cover open → amber "cover open, paper out, printer off-line".
|
||||
Tests replay the captured headerless reply. Full device notes: [[k200l-printer]].
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
type: concept
|
||||
tags: [parking, device, printer, transport, usb, escpos, provisioning]
|
||||
sources: []
|
||||
updated: 2026-08-30
|
||||
updated: 2026-09-09
|
||||
status: settled
|
||||
---
|
||||
|
||||
@@ -134,6 +134,11 @@ preselecting the first present device; a saved-but-unplugged path stays selectab
|
||||
"saved — not present now"; zero devices found falls back to the free-text path + a check-the-cable
|
||||
hint. The transport option label no longer hardcodes lp0.
|
||||
|
||||
> **Superseded 2026-09-09:** the XP-K200L DOES serve a status page — the same table as the Rongta,
|
||||
> at **`/prt_status.htm`**, without HTTP headers. It now has its own **`k200l`** driver (raw-socket
|
||||
> fetch; LAN = live cover/paper status, USB = reachability floor). See [[k200l-printer]]. The note
|
||||
> below is kept for the record.
|
||||
>
|
||||
> Driver-choice note for this clone: the ICS XP-K200L does NOT serve the Rongta `/prn_stat.htm`
|
||||
> status page (checked on hardware at 10.0.10.11 — print socket 9100 open, status page absent),
|
||||
> so on NETWORK the honest driver is **cashino** (reachability-only monitoring); under `rongta`
|
||||
@@ -189,6 +194,44 @@ itself is redone.
|
||||
> = `usblp_open`'s bidirectional read submit failed (printer endpoint state). The theory below
|
||||
> is kept for the record.
|
||||
|
||||
> **Lab reproduction FAILED to reproduce (2026-09-09, later the same day).** The same printer
|
||||
> unit on the `park-lab` box (a real Linux host, the booth's exact image `stage-2d9bb15`, the
|
||||
> prod compose with the `/dev/usb` bind-mount, the dev DB snapshot with the USB printer added as
|
||||
> `booth-receipt`, cards printed via the subscription "Reprint card" path): paper out → open
|
||||
> cover → load roll → close cover → reprint — **no error, status never stuck offline.** So the
|
||||
> printer, the app's USB transport and the compose wiring are cleared in isolation. What is left
|
||||
> is park-buzi's own environment (kernel/USB stack, the physical USB port/hub/cable/power at the
|
||||
> booth) and/or that container's *history* (weeks of uptime before the first failure — a leaked
|
||||
> handle needs a prior timeout to exist; a fresh container has none).
|
||||
>
|
||||
> **Status: park-buzi closed (staff shortage), everything shut down — evidence pending.** The
|
||||
> evidence is on the booth's DISK and survives shutdown/reboot: Docker keeps the container log
|
||||
> under `/var/lib/docker/containers/<id>/`, the kernel journal is persistent. **The day the box
|
||||
> powers on again (or lands on the bench), pull these FIRST, before deploying anything:**
|
||||
>
|
||||
> ```bash
|
||||
> # 1. the app's own record: the exact error text at every offline/ready transition
|
||||
> docker logs park-buzi-server-1 2>&1 | grep -E "device-monitor:.*printer.*-> (offline|ready)"
|
||||
> # 2. what the HOST kernel saw around those times (usblp errors, resets, disconnects)
|
||||
> sudo journalctl -k --since "-30 days" | grep -i -E "usblp|usb 1-|usb 2-|disconnect|reset"
|
||||
> # 3. the physical path: hub or direct port? (and note which PSU feeds the printer)
|
||||
> lsusb -t
|
||||
> ```
|
||||
>
|
||||
> Reading (1): `EBUSY` = a handle stuck inside the server process (usblp allows ONE opener;
|
||||
> fits "container restart fixes it") → look at the `withTimeout` leak below; `usb open timeout`
|
||||
> = `open()` blocks in the kernel; `EIO` = `usblp_open`'s bidirectional read submit failed
|
||||
> (printer/link state). Reading (2): any `USB disconnect` / `reset` / `usblp1: removed` at the
|
||||
> transition times means the LINK dropped at the booth (cable/port/hub/power) even though the
|
||||
> unit never dropped on the bench.
|
||||
>
|
||||
> **Follow-ups that need no booth (proposed, not built):** (a) `withTimeout` in
|
||||
> `printer-escpos.ts` abandons the FileHandle when an open/write times out — close it when the
|
||||
> underlying promise eventually settles, so a timeout can never leave the node held; (b) make
|
||||
> the monitor self-document the next occurrence: after N consecutive offline polls on a USB
|
||||
> printer, log the errno, `ls -la /dev/usb`, and who holds the node, so the next failure anywhere
|
||||
> in the fleet carries its own diagnosis without a person at the booth.
|
||||
|
||||
**Not confirmed on hardware — and now contradicted by the bench (above).** The original plan to
|
||||
confirm at the next occurrence, BEFORE restarting anything:
|
||||
```bash
|
||||
@@ -212,7 +255,8 @@ whether the Bus/Device number changes.
|
||||
passthrough + a udev rule pinning a stable symlink name — reintroduces the renumbering fragility
|
||||
the directory bind-mount was chosen to avoid, so only worth doing alongside (1)/(2), not instead.
|
||||
|
||||
**Printer identity — IDENTIFIED 2026-09-09.** The failing unit is on the dev bench: USB
|
||||
**Printer identity — IDENTIFIED 2026-09-09.** The failing unit is on the dev bench: a **K200L**
|
||||
(Xprinter/ICS XP-K200L family — see [[k200l-printer]] for the LAN setup and its status page): USB
|
||||
`1fc9:2016`, product string **"Printer POS-80"** (0x1fc9 = NXP, the printer's USB controller chip;
|
||||
"POS-80" is the generic 80 mm ESC/POS designation — no brand in the descriptor, which is why the app
|
||||
shows "Generic"). Seen via `usbipd list` on the Windows host (busid 8-1). **Dev-bench caveat:** the
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
type: reference
|
||||
tags: [parking, dev-environment, networking, wsl, troubleshooting]
|
||||
sources: []
|
||||
updated: 2026-06-15
|
||||
updated: 2026-09-09
|
||||
---
|
||||
|
||||
# WSL2 Dev Networking (for device testing)
|
||||
@@ -110,6 +110,15 @@ trap:
|
||||
Verified on hardware (2026-06-15): after the hook, `10.0.10.121` pings and the real [[lpr-camera]]
|
||||
Hikvision driver pulls a snapshot with **no** source-forcing (`localAddress` becomes optional).
|
||||
|
||||
> **It bit again, 2026-09-09 — and the fix was pinned to the wrong NIC.** Configuring the
|
||||
> [[k200l-printer]] meant adding `192.168.123.101` beside `10.0.10.203` on the mirrored LAN NIC;
|
||||
> WSL then sourced 10.0.10.x traffic from the 192.168.123 address: `ping` fine, every HTTP
|
||||
> connect timing out, `ip route get 10.0.10.7` showing `src 192.168.123.101`. `parking-net.service`
|
||||
> was active but pins **`eth1`**, and the mirrored LAN NIC is **`eth0`** on this box now — so the
|
||||
> boot fixer was a no-op. Run `deploy/wsl-fix-route-source.sh eth0` (and fix the unit's argument),
|
||||
> or `curl --interface 10.0.10.203 …` as a one-off. Interface names are not stable across WSL
|
||||
> reboots/NIC changes; the script accepts the NIC as an argument for exactly this reason.
|
||||
|
||||
## On the real appliance: multi-subnet is a deployment config, not a WSL hack
|
||||
|
||||
Production is a **dedicated hardened Linux appliance** ([[disk-os-hardening]]), so the WSL story
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
type: entity
|
||||
tags: [parking, hardware, printer, escpos, network, usb, status]
|
||||
sources: []
|
||||
updated: 2026-09-09
|
||||
---
|
||||
|
||||
# K200L thermal printer (Xprinter / ICS "XP-K200L") — the park-buzi unit
|
||||
|
||||
An 80 mm ESC/POS receipt printer, **USB + LAN**, sold under several names. Bottom label:
|
||||
*"THERMAL RECEIPT PRINTER — Model: K200L — Paper Width: 80mm — Print Speed: 200mm/s — Power
|
||||
Input: 24V 2.5A — Cash Drawer: 24V 1A — Interface: USB+LAN — Command Support: ESC/POS"*, serial
|
||||
`BLU2107080238`. Identified on the dev bench 2026-09-09; **it is the printer that "goes offline
|
||||
after a paper reload" at park-buzi** ([[printer-usb-transport]] §Field bug). The lab's older
|
||||
"ICS XP-K200L" (10.0.10.11, the 2026-07 USB truncation work) is the same family.
|
||||
|
||||
Three names for one device, all seen on the bench:
|
||||
|
||||
| Where | What it calls itself |
|
||||
| --- | --- |
|
||||
| bottom label | K200L |
|
||||
| USB descriptor (`lsusb`) | `1fc9:2016 NXP Semiconductors Printer-80` / "Printer POS-80" (0x1fc9 = the NXP controller chip; no brand) |
|
||||
| LAN board web UI | "J-Speed Ethernet Interface Module", "Ethernet WebConfig Version 1.02", copyright "POS" |
|
||||
|
||||
The app driver is **`k200l`** ("K200L 80mm thermal printer (Xprinter / ICS, USB+LAN)",
|
||||
`packages/devices/src/drivers/printer-k200l.ts`). It prints through the shared generic ESC/POS
|
||||
path (identical bytes, TCP 9100 or `usblp`) and **adds live status from the LAN board** — see
|
||||
below. Before 2026-09-09 this unit ran on the generic `escpos` driver (reachability only), which is
|
||||
why the app could never show its cover/paper state.
|
||||
|
||||
## Network setup (the evening that was never written down)
|
||||
|
||||
- **Factory address `192.168.123.100/24`, DHCP OFF.** Nothing announces it; the printer just sits
|
||||
there on a subnet nobody uses. To reach it, give the workstation a second address in
|
||||
`192.168.123.0/24` (Windows: adapter → IPv4 → Advanced → add `192.168.123.101`), then open
|
||||
`http://192.168.123.100/`.
|
||||
- The web configurator (port 80, **no authentication**) is a three-frame page: *Information*
|
||||
(`ip_info.htm`: MAC, IP, mask, gateway, DHCP on/off, DHCP timeout), *Configuration*
|
||||
(`ip_config.htm`: DHCP client on/off + timeout, fixed IP / mask / gateway as four octet fields,
|
||||
**Save**, Restore Default, cancel), *Printer Status* (`prt_status.htm`), *Printer Test*
|
||||
(`prt_test.htm`), and a **Restart** button in the menu.
|
||||
- **Set a fixed address on the device VLAN** (park-lab: `10.0.10.7/24`, gateway `10.0.10.1`) →
|
||||
Save → Restart; then remove the temporary `192.168.123.x` address from the workstation. Keep
|
||||
DHCP off — the app addresses printers by IP ([[rongta-printer]] §Deployment).
|
||||
- The self-test page (`prt_test.htm` / the "Print Test Page" button on the status page) prints
|
||||
the current network settings, so a unit with a forgotten address can be read back that way.
|
||||
- The board's HTTP server is **tiny**: the frameset reloads its frames every 1–3 s and the status
|
||||
page every 5 s, and it holds very few connections. **Close the browser tab while the app is
|
||||
polling**, or connections intermittently time out (seen on the bench: `ping` fine, port open,
|
||||
every second HTTP connect hanging while the page was open in a browser).
|
||||
|
||||
> **WSL gotcha while doing this (2026-09-09):** the dev box then carried BOTH `192.168.123.101`
|
||||
> and `10.0.10.203` on `eth0`, and WSL sourced 10.0.10.x traffic from the 192.168.123 address —
|
||||
> the exact [[wsl-dev-networking]] source-address bug, except `parking-net.service` pins `eth1`
|
||||
> and the mirrored LAN NIC is `eth0` now. Symptom: `ping` works, `curl` times out. Run the fix for
|
||||
> `eth0`, or drop the temporary address once the printer is moved.
|
||||
|
||||
## Live status — the `/prt_status.htm` page
|
||||
|
||||
The LAN board serves a five-row table the printer has already decoded from its own sensors:
|
||||
|
||||
```
|
||||
Cover Is Open Yes/No
|
||||
Cutter Error Yes/No
|
||||
Paper End Yes/No
|
||||
Paper Near End Yes/No
|
||||
Printer Off-Line Yes/No
|
||||
```
|
||||
|
||||
**Same rows, same `<TD>label</TD><TD>Yes|No</TD>` shape as the Rongta board's `/prn_stat.htm`**
|
||||
([[printer-status-monitoring]]) — only the path differs, which is why nobody found it in July
|
||||
(the Rongta driver looked for `/prn_stat.htm`, got nothing, and the unit was filed as "serves no
|
||||
status page → generic driver"). Verified on the bench: cover open → `Cover Is Open Yes`, `Paper End
|
||||
Yes`, `Printer Off-Line Yes` within a refresh; cover closed → all `No`.
|
||||
|
||||
**Quirk that needs its own fetch code:** the board's HTTP reply has **no status line and no
|
||||
headers** — the body starts at byte 0 (HTTP/0.9 style). Browsers render it; `curl` reports
|
||||
`000` with an empty body; Node's `http` client rejects it with *"Parse Error: Expected HTTP/, RTSP/
|
||||
or ICE/"*. So the `k200l` driver reads the page over a **raw TCP socket** (`GET … HTTP/1.0`, read
|
||||
until the board closes) and accepts both the headerless reply and a proper one. This is the second
|
||||
reason the K200L has its own driver rather than a path option on the Rongta one.
|
||||
|
||||
Status mapping (mirrors the Rongta driver, [[printer-status-monitoring]]): board unreachable /
|
||||
timeout → **offline**; page reachable but not the table (non-200, the index page) → **degraded
|
||||
"unexpected status page"**, never ready off a page we didn't read; any Yes → **degraded** naming
|
||||
the faults ("cover open, paper out, printer off-line"); all No → **ready**. Over **USB** there is
|
||||
no page: reachability floor only (ready/offline), same as a USB Rongta.
|
||||
|
||||
## What this means for the park-buzi bug
|
||||
|
||||
At park-buzi this unit ran **over USB** on the generic driver, i.e. monitored by "does
|
||||
`/dev/usb/lpN` open". A cover-open / paper-out condition **never showed in the app at all** — the
|
||||
badge stayed green. So the operators' "printer goes offline after reloading paper" was not the
|
||||
cover state being reported; it was a genuine probe failure whose errno is still unread (site shut
|
||||
down — [[printer-usb-transport]] §Field bug has the commands to pull first). Running the unit on
|
||||
**LAN with the `k200l` driver** would give the booth a real amber "cover open / paper out" while
|
||||
the roll is changed, and removes the `usblp` path from the equation altogether — a strong reason to
|
||||
cable it to the device VLAN when the site reopens.
|
||||
|
||||
Related: [[rongta-printer]] · [[printer-status-monitoring]] · [[printer-usb-transport]] ·
|
||||
[[printer-roles-failover]] · [[network-isolation]] · [[wsl-dev-networking]] · [[site-device-installation]]
|
||||
@@ -2,7 +2,7 @@
|
||||
type: entity
|
||||
tags: [parking, hardware, printer, device]
|
||||
sources: []
|
||||
updated: 2026-06-19
|
||||
updated: 2026-09-09
|
||||
---
|
||||
|
||||
# Rongta 80mm thermal printer
|
||||
@@ -55,6 +55,10 @@ many ESC/POS-compatible OEM clones that share its firmware). Driver `rongta` in
|
||||
- **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
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ Counts: 4 sources · 19 entities · 47 concepts · 8 decision records.
|
||||
- [[dingtian-relay]] — ✅ CHOSEN access controller; decoupled inputs solve the button blocker (driver verified on hardware); spare relays drive aux outputs (`setAux`).
|
||||
- [[hikvision-radar]] — vehicle-presence radar on a Dingtian input; the entry presence gate (per-input active-level caveat).
|
||||
- [[rongta-printer]] — ✅ CHOSEN 80mm thermal printer; ESC/POS over raw TCP 9100 (or local USB, see [[printer-usb-transport]]); driver written, one unit reachable at 10.0.10.6.
|
||||
- [[k200l-printer]] — the park-buzi printer identified (2026-09-09): Xprinter/ICS K200L, USB id 1fc9:2016 "POS-80", J-Speed LAN board at 192.168.123.100 (DHCP off, web config on :80); status page `/prt_status.htm` (Rongta's rows, headerless HTTP) → own `k200l` driver with raw-socket fetch; over USB reachability only, so cover-open never showed at park-buzi.
|
||||
- [[bom]] — reference bill of materials (barrier, loops, controller, readers, payment, host, network).
|
||||
|
||||
## Concepts — foundational forces
|
||||
@@ -65,6 +66,7 @@ Counts: 4 sources · 19 entities · 47 concepts · 8 decision records.
|
||||
- [[device-adapter-pattern]] — business logic talks to interfaces; swap hardware → new adapter.
|
||||
- [[device-registry]] — catalog of selectable drivers per category (admin-configurable).
|
||||
- [[first-run-setup]] — admin adds controllers + binds readers/cameras to relays from the catalog at install.
|
||||
- [[site-device-installation]] — FIELD RUNBOOK (2026-09-09): per device — factory address + credentials, the tool needed, what the wizard configures itself vs what is done by hand on the device, known traps; address plan, order of work on site, gaps to fill. Dingtian relay, DT-008 readers, Hikvision camera, radar, K200L / Rongta / Cashino printers.
|
||||
- [[device-input-flow]] — button → device push → backend decides → relay; backend is source of truth.
|
||||
- [[device-discovery]] — optional driver capability to scan the LAN (no current driver uses it; UHPPOTE was the example).
|
||||
- [[barrier-not-a-door]] — never timed-close a barrier; safety lives in barrier firmware.
|
||||
|
||||
Reference in New Issue
Block a user