Public Access
Document the printing subsystem
Covers the roll/job colour rule that caused the "change it to Monochrome media" failures, the tri-state health result, and why network status is unavailable on this hardware.
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
# Badge printing
|
||||||
|
|
||||||
|
The app renders the badge and sends it to the Brother QL-820NWB over TCP port
|
||||||
|
9100, speaking Brother's raster command language directly. No CUPS, no driver,
|
||||||
|
no print dialog, and no Python.
|
||||||
|
|
||||||
|
## Why not AirPrint
|
||||||
|
|
||||||
|
AirPrint from the kiosk browser means iPadOS renders the badge from print CSS
|
||||||
|
and picks the page geometry from what the printer advertises. That costs exact
|
||||||
|
control of the 60.96 mm print width, makes two-colour DK-22251 output
|
||||||
|
impossible, and cannot suppress the print dialog — so a staff member has to tap
|
||||||
|
"Print" for every visitor. Fine for a staffed desk, useless for unattended
|
||||||
|
sign-in.
|
||||||
|
|
||||||
|
Doing it server-side also means the kiosk device stops mattering. An iPad, an
|
||||||
|
old Android tablet, a browser on a NUC: they all just POST.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/printing/
|
||||||
|
ql-raster.js command language: media, cut, expanded mode, bit packing
|
||||||
|
ql-status.js decoder for the 32-byte status frames
|
||||||
|
ql-transport.js TCP client for port 9100
|
||||||
|
ql-print.js printPng / printerHealth, plus the serial queue
|
||||||
|
src/printer.js badge layout and rendering (unchanged by this work)
|
||||||
|
```
|
||||||
|
|
||||||
|
Protocol details follow Brother's *Raster Command Reference, QL-800/810W/820NWB
|
||||||
|
v1.01*. Where that manual and pklaus/brother_ql disagree, the manual wins;
|
||||||
|
brother_ql targets older models and several of its defaults are wrong for this
|
||||||
|
hardware (200-byte invalidate instead of 400, the print-quality bit set during
|
||||||
|
two-colour jobs, the media-length valid bit asserted for continuous tape).
|
||||||
|
|
||||||
|
## The roll setting is not cosmetic
|
||||||
|
|
||||||
|
`sites.printer_label` must match the roll physically in the machine:
|
||||||
|
|
||||||
|
| Roll | Setting |
|
||||||
|
|---|---|
|
||||||
|
| DK-22205, 62 mm white | `62` |
|
||||||
|
| DK-22251, 62 mm black/red | `62red` |
|
||||||
|
|
||||||
|
This is enforced by the printer, not by us. A monochrome job sent to a
|
||||||
|
black/red roll is refused outright, and vice versa. It applies even when the
|
||||||
|
badge has no red on it — with `62red` set, every job is built as two-colour and
|
||||||
|
the red plane is simply empty.
|
||||||
|
|
||||||
|
The printer's own error message is misleading here. It says "change it to
|
||||||
|
Monochrome media", which points at a driver setting that doesn't exist in this
|
||||||
|
setup. The fix is the roll dropdown in the admin console, or swapping the roll.
|
||||||
|
|
||||||
|
## Status, and why there isn't much
|
||||||
|
|
||||||
|
Manual section 5.9: over a network connection the print data is sent as-is and
|
||||||
|
nothing comes back. Status frames only arrive over USB. brother_ql documents the
|
||||||
|
same limitation for its network backend.
|
||||||
|
|
||||||
|
So `printerHealth` returns a tri-state `ready`:
|
||||||
|
|
||||||
|
- `true` — the printer answered and everything is fine
|
||||||
|
- `false` — it answered and something is wrong
|
||||||
|
- `null` — reachable, but it won't say
|
||||||
|
|
||||||
|
`null` is the normal answer here. Show it as amber/unknown in the admin console;
|
||||||
|
colouring it green claims something we don't know. Likewise `printPng` returns
|
||||||
|
`confirmed: false` when no status came back: the bytes were delivered, but
|
||||||
|
whether paper moved is unknown.
|
||||||
|
|
||||||
|
Consequence worth accepting: a jammed or empty printer cannot be detected. The
|
||||||
|
practical signal is a visitor saying no badge appeared. That is tolerable
|
||||||
|
because the evacuation record is written before printing is attempted. If real
|
||||||
|
roll monitoring is ever needed, the lead to follow is SNMP — the Brother Status
|
||||||
|
Monitor on Windows gets roll state over the network somehow, and it is not
|
||||||
|
using port 9100.
|
||||||
|
|
||||||
|
## Concurrency
|
||||||
|
|
||||||
|
The QL accepts one TCP connection at a time and has no job spooler worth the
|
||||||
|
name. Two visitors signing in together would otherwise produce a refused
|
||||||
|
connection, half a label, or both. Every job goes through a serial queue keyed
|
||||||
|
on printer host, in `ql-print.js`.
|
||||||
|
|
||||||
|
That queue is in-process. Running more than one app instance against one printer
|
||||||
|
would need a lock in SQLite instead.
|
||||||
|
|
||||||
|
## Geometry
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Print head | 720 pins, 60.96 mm at 300 dpi |
|
||||||
|
| 62 mm roll printable | 696 pins, 58.9 mm |
|
||||||
|
| Margins | 12 pins each side |
|
||||||
|
| Feed margin | 35 dots (3 mm), the documented minimum |
|
||||||
|
| Length | 150 to 11811 raster lines (12.7 mm to 1 m) |
|
||||||
|
|
||||||
|
Every raster line sent is the full 90 bytes regardless of the roll. Narrower
|
||||||
|
media just means more pins miss the paper.
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
Reachability and roll state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec visitor-signin node -e "
|
||||||
|
import('./src/printing/ql-print.js').then(async m =>
|
||||||
|
console.log(await m.printerHealth({ host: '10.142.177.169', label: '62red' })));
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
If a label never appears, work down this list:
|
||||||
|
|
||||||
|
**Nothing at all, `Cannot reach printer`.** Routing or firewall between the
|
||||||
|
container and the printer. Try `nc -vz IP 9100` from the Ubuntu host; if that
|
||||||
|
works but the container doesn't, it is Docker networking.
|
||||||
|
|
||||||
|
**Connects but silent.** Normal. See above.
|
||||||
|
|
||||||
|
**Media mismatch error.** The roll setting and the physical roll disagree.
|
||||||
|
|
||||||
|
**Labels come out blank.** Roll in backwards, or a non-Brother roll with no end
|
||||||
|
sensor. The printer reports no error for this.
|
||||||
|
|
||||||
|
**Everything mirrored.** The bit packing is wrong. The head clocks each raster
|
||||||
|
line out right-to-left; see `packRow`.
|
||||||
|
|
||||||
|
**First label after a power cycle is misaligned.** Feed one label from the front
|
||||||
|
panel before the first sign-in of the day.
|
||||||
|
|
||||||
|
## Known rough edge
|
||||||
|
|
||||||
|
Photos are not dithered. `drawBadge` draws them straight and the plane
|
||||||
|
conversion thresholds at luminance 180. A thermal head has no grey, so faces
|
||||||
|
come out blotchy. The fix is Floyd–Steinberg on the photo region before it goes
|
||||||
|
on the canvas, inside `drawBadge`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Created by: Jess Rogerson (yelling commands at Claude.AI)
|
||||||
Reference in New Issue
Block a user