A generic Brother P-touch IPP Everywhere print server, built on PAPPL. It exposes a P-touch label printer as a driverless IPP Everywhere / AirPrint device so any client can discover it and print without a vendor driver.
The Brother raster protocol and device table are reused from hannesweisbach/ptouch-print (GPL). See LICENSE.
The project is built in milestones. Only the Brother PT-2730 is hardware-certified; the print path currently emits PT-2730 protocol for every device.
| Milestone | Scope | State |
|---|---|---|
| M0 | Hardware spike: confirm the raster bytes and the usb:// hang on real hardware |
Done |
| M1 | Pure C core: device table, protocol command builders, raster packing, status parse | Done |
| M2 | PAPPL adapter and read path: custom ptouch:// libusb scheme, media/status callback |
Done |
| M3 | Print path: raster callbacks that print and cut on the PT-2730 | In progress |
| M4 | Cutter modes and width guard (PTOUCH_* configuration) |
Pending |
| M5 | Packaging | Pending |
M3 raster callbacks print and cut a label on the PT-2730 today (init / rasterstart / sendraster per line / eject). The exact print geometry is still being finished: no-scaling fit, variable label length, and head-limiting behaviour are not yet final.
Three layers:
-
Pure C core (
src/tables.c,src/protocol.c,src/raster.c,src/status.c,src/cutter.c). Libc only, no hardware, no PAPPL. All Brother byte sequences live inprotocol.c; raster packing centers a scanline into the 16-byte (128-dot) raster line inraster.c; the device table and per-model flags live intables.c. This layer builds and unit-tests on any machine. -
PAPPL driver (
src/driver.c). Registers the IPP printer, fills the driver data (media, resolution, finishings), reads loaded-tape status, and drives the raster print callbacks (rstartjob/rwriteline/rendjob). -
Custom
ptouch://libusb scheme (src/device_usb.c). PAPPL's built-inusb://scheme issues an IEEE-1284 device-ID probe that hangs on the PT-2730 (confirmed in M0). This project registers its ownptouch://PAPPL_DEVTYPE_CUSTOM_LOCALscheme over raw libusb with bounded timeouts, and never auto-adds the built-inusb://device.
src/tables.c recognizes the P-touch USB ids below (transcribed from upstream
ptdevs[]). Only the PT-2730 is print-supported and hardware-certified. Every
other entry is recognized and discoverable, but is not print-supported: the
raster path emits PT-2730 protocol (uncompressed, 128-dot head) regardless of the
matched model, and per-model flag dispatch (PackBits, P700 init) does not exist
yet. All known models are 180 dpi / 128 px max.
| Model | USB id | flags | Print-supported |
|---|---|---|---|
| PT-2420PC | 04f9:2007 | RASTER_PACKBITS |
recognized only |
| PT-1230PC | 04f9:202c | NONE |
recognized only |
| PT-2430PC | 04f9:202d | NONE |
recognized only |
| PT-1230PC (PLite) | 04f9:2030 | PLITE |
recognized only |
| PT-2430PC (PLite) | 04f9:2031 | PLITE |
recognized only |
| PT-2730 | 04f9:2041 | NONE |
certified |
| PT-E500 | 04f9:205f | RASTER_PACKBITS |
recognized only |
| PT-H500 | 04f9:205e | RASTER_PACKBITS | P700_INIT |
recognized only |
| PT-P700 | 04f9:2061 | RASTER_PACKBITS | P700_INIT |
recognized only |
| PT-P700 (PLite) | 04f9:2064 | PLITE |
recognized only |
| PT-P750W | 04f9:2062 | RASTER_PACKBITS | P700_INIT |
recognized only |
| PT-P750W (PLite) | 04f9:2065 | PLITE |
recognized only |
| PT-D450 | 04f9:2073 | RASTER_PACKBITS |
recognized only |
| PT-D600 | 04f9:2074 | RASTER_PACKBITS |
recognized only |
To add a model, see docs/ADDING-A-MODEL.md.
The pure C core builds and unit-tests anywhere with CMake and a C11 compiler. No hardware or PAPPL needed:
cmake -S . -B build && cmake --build build && ctest --test-dir buildThis builds ptcore and runs the five core test suites (tables, protocol,
raster, status, cutter).
The full ptouch-app executable builds only where PAPPL and libusb-1.0 are
present (pkg-config finds pappl and libusb-1.0). On a machine without them,
CMake skips the app target and still builds the core and tests. The app is built
and exercised against PT-2730 hardware in a container; see Dockerfile.
The printer advertises continuous-roll media (tape-width-fixed, length-variable
roll_min/roll_max range) and uses print-scaling=none, so a label prints
1:1. For a label to come out at its true size, a client must:
- send the image at the printer resolution, 180 dpi (a PNG with no DPI metadata is placed at a default ppi and will not fill the requested media), and
- request a
media-colsize matching the image (tape imageable width x label length). The printer reports the loaded tape's printable width viamedia-ready; note media is advertised at printable, not physical, width (a 24 mm tape images ~18 mm, the 128-dot head).
The print path is configured entirely through environment variables (no config file or volume). The driver reads them per job.
| Variable | Values | Meaning |
|---|---|---|
PTOUCH_CUT_MODE |
each (default) / end / none |
Cut policy for a batch: cut after every label, only after the last, or never |
PTOUCH_PRECUT |
1 (default) / 0 |
Feed and cut a leader before the first label of a batch |
PTOUCH_WIDTH_GUARD |
strict (default) |
Refuse to print when the rendered width does not match the loaded tape. Only strict is implemented; any other value falls back to strict |
PTOUCH_DEVICE_URI overrides device discovery with an explicit URI (e.g.
socket://127.0.0.1:9100) for testing without a PT-2730; the printer registers
but only connects on an actual print.
The deploy image is published to GHCR as
ghcr.io/pfa230/ptouch-print-server:edge (the moving tag) and a per-commit
sha-<commit> tag for rollback. It is built and pushed by
.github/workflows/deploy.yml on every push to main; pull requests build and
run an IPP smoke test but do not push.
The image is a multi-stage build (Dockerfile.deploy): PAPPL 1.4.11 and the app
compile in a Debian build stage, and only the runtime libraries, avahi-daemon,
and dbus ship in the debian-bookworm-slim runtime stage. The entrypoint
starts a system dbus and Avahi (DNS-SD on the container's own LAN IP), then runs
ptouch-app server on port 8000. The container has no persistent state, so no
volume is needed.
Run it on the LAN with its own IP and the PT-2730 passed through:
services:
ptouch:
image: ghcr.io/pfa230/ptouch-print-server:edge
container_name: ptouch
restart: unless-stopped
hostname: ptouch
environment:
PTOUCH_CUT_MODE: each
PTOUCH_PRECUT: "1"
PTOUCH_WIDTH_GUARD: strict
devices:
- /dev/bus/usb:/dev/bus/usb # PT-2730 USB passthrough
device_cgroup_rules:
- 'c 189:* rwm' # allow libusb to re-claim across replug
networks:
br0:
ipv4_address: 10.10.1.40 # its own LAN IP (adjust to your network)
networks:
br0:
external: trueThe PT-2730 USB interface can be held by only one process. A dev/spike container holding the device must be stopped before this container starts; both cannot own the device at once.
This project is licensed under the GNU General Public License v3.0 only
(GPL-3.0-only). The full text is in LICENSE.
It is GPL because it reuses Brother P-touch raster protocol and device-table code from hannesweisbach/ptouch-print (GPLv3, by Dominic Radermacher). Credit and thanks to that upstream project.