Driver stack for a VEVOR Y43BT 4x6 thermal label printer (USB 5958:0041,
vendor string Yxwl) on Arch Linux with CUPS. It renders PDFs and images to
TSPL and prints them, both from the shell and from any application's Ctrl+P.
The VEVOR branding appears nowhere on the device - only in the Windows driver -
so 5958:0041 / Yxwl is how you actually identify it.
The printer speaks TSPL at 203 dpi. A 4x6 label is 800 x 1200 dots of printable area (the full label is 812 x 1218). It has two USB interfaces: interface 0 is a small mass-storage "Driver" partition holding a link to VEVOR's download page, interface 1 is the actual printer.
| file | role |
|---|---|
print-label.py |
renders a PDF/image to a 1-bit 800x1200 bitmap and wraps it in TSPL |
cups/rastertoy43bt |
CUPS filter: PDF on stdin -> TSPL on stdout |
cups/y43bt.ppd |
PPD declaring 4x6 media, 203 dpi, and the filter hookup |
install.sh |
installs all of the above and creates the queues |
shell/y43bt-aliases.zsh |
label, labeldark, labelpv, ... |
./install.shRequires cups, poppler (for pdftoppm) and a system-wide python-pillow.
Pillow must not be a --user install: the filter runs as the cups user and
will not see it.
label shipping.pdf # print, calibrated defaults
label shipping.pdf -d 13 # darker
label shipping.pdf -n 2 # two copies
labelpv shipping.pdf # render a preview PNG, print nothingOr just hit Ctrl+P in any application and pick Label_4x6, which the
installer sets as the system default.
Settled on DENSITY 11 / SPEED 2.
Density is the "heat" setting, DENSITY 0-15, higher = darker.
Judge a density change by the small return-address text rotated 90 degrees in the top-right corner, not by the barcode. Those strokes are 2-3 px in both directions and merge before anything else on the label does. The barcode's narrowest real gap is a forgiving 2 px - far wider than a synthetic Code128 test suggests.
| setting | result |
|---|---|
| DENSITY 10 | read faint |
| DENSITY 11 / SPEED 3 | over-corrected, thin and over-separated |
| DENSITY 12 / SPEED 2 | still bled the rotated strokes shut |
| DENSITY 13 | merged those strokes badly |
| DENSITY 11 / SPEED 2 | settled here |
Prefer lowering SPEED over raising DENSITY. The head fires a whole dot row at once, so vertical spacing comes from paper feed; rotated text is drawn by feed motion, where dwell time sharpens strokes while extra heat merely spreads them.
Change ONE of density / render-pipeline at a time. Dropping density and removing a resample step together compounded and overshot, costing a round of calibration.
Bleeding is a heat problem, not a rendering problem. The 1-bit bitmap keeps
those strokes cleanly separated at thresholds 128, 110 and 90, and even 90
removes only ~13% of the black. Only DENSITY and SPEED move it. The 2D
DataMatrix is fully threshold-independent - identical black-pixel count at every
cutoff - so --threshold will never help it either.
PDFs are rasterised straight from their vectors at the final pixel size
(pdftoppm -scale-to-x/-scale-to-y) rather than rendered at 203 dpi and
resampled. Measured on a Code128 test, resampling fattened bars ~4% (134 vs 129
black px per scanline). Don't reintroduce a LANCZOS resize for PDFs.
install.sh creates two queues pointing at the same device:
| queue | type | used by |
|---|---|---|
VEVOR_Y43BT |
raw | print-label.py, which sends TSPL with lp -o raw |
Label_4x6 |
driver (PPD) | system default; every GUI app |
This is forced, not tidiness. CUPS 2.4 silently drops -o raw jobs sent to a
queue that has a PPD attached. Attaching the PPD to the raw queue breaks
print-label.py with no error anywhere: the job logs Job completed in the
same second it was queued, no backend is ever started, and zero bytes reach the
device. If label ever goes silent, check that nothing attached a PPD to the
raw queue.
*cupsFilter2is required in the PPD, not just*cupsFilter. With only the latter, cups-filters reads the PPD as a PostScript device and routes the job throughpdftops, which fails.- The filter must re-suffix its input
.pdf. As the last filter in the chain it receives data on stdin, or a spool file named liked00008-001- either way with no extension.print-label.pypicks its PDF path by extension, so without this Pillow tries to open a PDF as an image. - CUPS runs filters as the
cupsuser (uid 209), notlp.sudo -u lpfails with "unknown user";lpexists only as a group. - The renderer cannot live in a 0700 home directory - the
cupsuser cannot traverse it. It installs to/usr/local/lib/y43bt/with a symlink from~/.local/bin/, so both paths share one file and the density defaults cannot drift apart. --previewstill prints. It saves the bitmap in addition to printing. Only-osuppresses the job;labelpvpasses both.- This printer answers no status queries. Every TSPL status form tried
(
~!@,~!T,~!F,~!I, and the ESC forms) returns nothing, despite the USB descriptor advertising bidirectional support. Infer state from the CUPS queue instead.
Render only, no print:
print-label.py label.pdf --preview /tmp/out.png -o /dev/nullTo exercise the whole CUPS chain, point a scratch queue at a file:
echo 'FileDevice Yes' | sudo tee -a /etc/cups/cups-files.conf
sudo systemctl restart cups
sudo touch /tmp/cap.out && sudo chmod 666 /tmp/cap.out # backend won't create it
sudo lpadmin -p TEST -v file:///tmp/cap.out -P /usr/share/cups/model/y43bt.ppd -E
lp -d TEST label.pdf
head -c 60 /tmp/cap.out # expect: SIZE 4.0,6.0 / DENSITY 11 / SPEED 2Remember to remove the queue and the FileDevice line afterwards.
cupsfilter is not a valid test. It resolves PPD filters differently from
the daemon and fails on a chain that works correctly under cupsd.