Public Access
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.
141 lines
5.3 KiB
Markdown
141 lines
5.3 KiB
Markdown
# 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)
|