Skip to content

strausmann/Label-Printer-Hub

Label Printer Hub

CI CodeQL codecov CLA assistant License: MIT Conventional Commits semantic-release GitHub Issues

Self-hosted multi-printer hub for Brother PT-Series and QL-Series label printers. Pull-mode (user scans barcode) and push-mode (Spoolman/Grocy webhooks). Integrates with Snipe-IT, Grocy, Spoolman. Plugin-based architecture for additional printer models. PWA-ready for smartphone use.

Status

Early development. See open issues for progress and the master tracking issue #22 for the phase roadmap.

This project is being designed against the Brother PT-E550W/P750W/P710BT Raster Command Reference v1.02 and QL-800/810W/820NWB Raster Command Reference. Hardware tested: Brother PT-P750W (verified). Brother QL-820NWBc (in progress).

Features (planned)

  • Multi-printer support via plugin architecture (PT-Series and QL-Series, more on request)
  • Pull-mode: Open the web UI on your phone, scan a barcode, hit print
  • Push-mode: Spoolman/Grocy webhook → automatic label print
  • App integrations: Snipe-IT (asset tags), Grocy (product labels), Spoolman (3D-print spool labels)
  • Print queue with pause/resume/cancel/retry/priority operations
  • Live status pages per printer via Server-Sent Events (no page reload)
  • Tape detection via Brother native status block (no manual configuration)
  • PWA-installable for smartphone use
  • Pluggable: drop a new printer_models/your_model.py to add a new device

Tech Stack

Two-container split: backend (printer protocols, queue, API) and frontend (UI, PWA) are separate containers. They release together at the same semver version, communicate over HTTP/JSON, and the frontend proxies SSE for live status updates.

  • Backend (label-printer-hub-backend): Python 3.12+, FastAPI, SQLModel (SQLite), asyncio
  • Printer protocols: nbuchwitz/ptouch (PT-Series), pklaus/brother_ql (QL-Series), pysnmp for status polling
  • Frontend (label-printer-hub-frontend): Go web server, Tailwind CSS, HTMX, PWA (manifest + service worker + Web Notifications API)
  • Container: Docker (multi-stage per service), GHCR + Docker Hub publishing, multi-arch (amd64 + arm64)
  • CI/CD: GitHub Actions, semantic-release, Dependabot

Container images and tags

Every stable release publishes to GitHub Container Registry (GHCR) and Docker Hub with this tag scheme:

Tag Example for 1.0.0 Use when
1.0.0 exact version You want full reproducibility
1.0 latest patch in 1.0.x Auto-update bug fixes
1 latest minor.patch in 1.x.x Stay on major version, get features
latest most recent stable You're fine with anything new

Pre-releases (1.0.0-rc.1 etc.) publish only the full version tag — never latest, <major>, or <major>.<minor> — so a pre-release can never silently become the default.

Both registries receive identical multi-arch images (linux/amd64, linux/arm64).

Quick Start

See examples/README.md for sample compose files (standalone / Traefik / Pangolin / Caddy). For early-development testing of the REST API alone — without the frontend — see examples/compose.backend-only.yml:

# Backend-only (builds the image from source — no GHCR pull required):
git clone --branch main https://github.com/strausmann/label-printer-hub.git
cd label-printer-hub
cp backend/.env.example .env
$EDITOR .env                # set PRINTER_HUB_PT750W_HOST and friends
docker compose -f examples/compose.backend-only.yml up -d --build

# REST API smoke
curl http://localhost:8090/healthz
curl -X POST http://localhost:8090/print -H 'Content-Type: application/json' \
  -d '{"template_id":"qr-only-12mm","data":{"title":"Smoke","primary_id":"SMOKE-001","qr_payload":"https://example.test"}}'

To build and run the full stack (backend + frontend) from source without any real printer hardware, use the smoke-test compose file:

# Full-stack local smoke test (mock printer, no hardware required):
docker compose -f dev/docker-compose.smoke.yml up --build

# UI is served at http://localhost:8080
# Verify both services are healthy:
curl http://localhost:8080/healthz   # frontend → backend_reachable: true

REST API surface

Method Path Purpose Body
POST /print Submit a print job PrintRequest (see below)
GET /jobs/{job_id} Poll job status (includes live SNMP block while printing)
POST /jobs/{job_id}/resume Resume a job paused by tape mismatch (after the user changed the tape physically)
POST /printer/resume Resume the printer queue after a recoverable error halted it (tape empty / cover open / offline)
GET /healthz Liveness probe for orchestrators

POST /print request body

{
  "template_id": "qr-only-12mm",         // id from app/seed/templates/<id>.yaml
  // Exactly one of `lookup` or `data` is required.
  "lookup":  { "app": "snipeit", "identifier": "123" },
  "data":    { "title": "Asset 123",
               "primary_id": "ASSET-123",
               "qr_payload": "https://snipe.example/assets/123",
               "secondary": ["optional", "extra lines"] },
  "options": { "copies": 1,              // 1..10, default 1
               "auto_cut": true,
               "high_resolution": false },
  // Default "fail" → synchronous 409 + error_detail{expected_mm, loaded_mm}.
  // "queue"        → 202 + job_id; job lands in PAUSED until the user POSTs
  //                  /jobs/{id}/resume after physically swapping the tape.
  "on_tape_mismatch": "fail"
}

Synchronous error codes (POST /print)

HTTP error_code When
404 template_not_found unknown template_id
409 tape_mismatch loaded tape ≠ template tape, on_tape_mismatch="fail"
409 tape_empty preflight detects no media
409 printer_cover_open preflight detects cover open
502 integration_lookup_failed integration plugin raised
503 printer_offline SNMP preflight could not reach the printer

tape_mismatch responses include error_detail: {expected_mm, loaded_mm} so the client can build a "swap the tape" dialog.

Environment variables

Full reference lives in backend/.env.example. The most-used variables grouped by purpose:

Variable Default Purpose
PRINTER_HUB_DATABASE_URL sqlite:////data/printer-hub.db SQLite path (Phase-5 persistence; ignored today)
PRINTER_HUB_PT750W_HOST empty Brother PT-P750W IP/hostname (required when printer_backend=ptouch)
PRINTER_HUB_PT750W_PORT 9100 TCP print port
PRINTER_HUB_QL820_HOST empty Brother QL-820NWB IP (when QL backend lands)
PRINTER_HUB_QL820_PORT 9100 TCP print port
PRINTER_HUB_PRINTER_BACKEND ptouch Transport: ptouch | mock | third-party entry-point id
PRINTER_HUB_PRINTER_MODEL PT-P750W Fallback model id when SNMP discovery is off / unreachable
PRINTER_HUB_PRINTER_DISCOVER_VIA_SNMP true SNMP-first model discovery via Brother private OID, fall back to PRINTER_HUB_PRINTER_MODEL on failure
PRINTER_HUB_PRINTER_SNMP_COMMUNITY public SNMPv2c community (LAN-only — read-only)
PRINTER_HUB_PRINTER_QUEUE_TIMEOUT_S 30 Graceful shutdown timeout for the print queue
PRINTER_HUB_WEBHOOK_API_KEY empty Bearer for inbound integration webhooks (≥ 32 chars; generate with openssl rand -hex 32)
PRINTER_HUB_SNIPEIT_URL empty Snipe-IT base URL
PRINTER_HUB_SNIPEIT_API_KEY empty Snipe-IT bearer token
PRINTER_HUB_GROCY_URL empty Grocy base URL
PRINTER_HUB_GROCY_API_KEY empty Grocy API key
PRINTER_HUB_SPOOLMAN_URL empty Spoolman base URL (no auth)
PRINTER_HUB_SERVER_PORT 8090 Internal port (overridden to 8000 in the container — see Dockerfile)
PRINTER_HUB_LOG_LEVEL INFO DEBUG / INFO / WARNING / ERROR

All variables share the PRINTER_HUB_ prefix and map 1:1 to the Settings model in backend/app/config.py.

Documentation

In this repository (engineering — change with code):

On the wiki (tutorials and platform recipes — community-friendly):

Live API reference (when running): /openapi.json, /docs (Swagger UI), /redoc — see ADR 0011.

Contributing

Contributions are welcome — especially printer model plugins. See CONTRIBUTING.md for the workflow (Conventional Commits, TDD, semantic-release).

Trademarks and disclaimer

Brother, P-touch, PT-Series, and QL-Series are trademarks or registered trademarks of Brother Industries, Ltd. All other trademarks are the property of their respective owners.

This project is not affiliated with, endorsed by, or sponsored by Brother Industries, Ltd. It is an independent open-source project that interoperates with Brother label printers via documented protocols (Brother Raster Command Reference, IEEE 802.3, RFC 3805 SNMP Printer-MIB, RFC 8011 IPP).

"Brother" is used in this README solely for the purpose of describing the hardware that this software is compatible with. No commercial use of the Brother trademarks is intended.

License

This project is licensed under the MIT License — see LICENSE for details.

The Brother Raster Command Reference PDFs distributed in this repository (under docs/research/brother-spec/, if present) remain the property of Brother Industries, Ltd. Their inclusion is a verbatim, unmodified copy redistributed under fair use for development reference. The Brother documentation license terms apply to those files.

Acknowledgements

About

Self-hosted label printer hub for Brother PT-Series and QL-Series — multi-printer queue management, Snipe-IT/Grocy/Spoolman integration, plugin-based architecture, PWA-ready

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors