thermark 0.33.0

Local sticker printing for pocket thermal printers over BLE/USB — QR links, inventory tags, badges, photos; no vendor app
Documentation

thermark

Print labels without the vendor app. Local, scriptable QR codes, Wi-Fi stickers, inventory tags, and text for pocket thermal printers. No cloud or account needed to print.

Download for macOS or Linux · Watch the offline demo · Report your printer

Hardware-tested: B1 over Bluetooth LE. USB and other printer profiles are experimental. thermark prints monochrome labels; it does not support colour printing.

Install and print your first label

On macOS (Apple Silicon or Intel), install the prebuilt BLE binary:

brew install kahwee/thermark/thermark

For Linux, or macOS without Homebrew, use the prebuilt downloads. Choose ble for Bluetooth or full for experimental USB serial support. No Rust compiler is needed for a prebuilt download.

Turn on your B1, load 50×30 mm labels, and quit the vendor app. Then:

thermark scan --save
thermark doctor --use-config
thermark qr --url "https://example.com" --text "Hello, label!" --label 50x30

If several printers appear, follow the scan selection hint. On macOS, allow Bluetooth access for your terminal when requested. See connection troubleshooting if discovery fails.

Try it without a printer

thermark qr --url "https://example.com" --text "Hello, label!" \
  --model b1 --label 50x30 --save hello-label.png --no-print

This saves a real PNG locally without connecting to hardware. Scan its QR or inspect the layout before spending a label.

Guest Wi-Fi Inventory URL + text
Demo Wi-Fi label with fake credentials Demo inventory label Demo URL label

These are rendered previews using public demo data, not photographs of prints.

What it does

  • Guest Wi-Fi stickers: network name plus a QR code to join.
  • URL stickers: a scannable link with human-readable text.
  • Plain text, inventory, badge, and line-art labels.
  • Exact physical sizing from a CLI or Rust program.
  • Local previews before using paper.

Support

Model family / path Status
B1 over BLE Hardware-tested
B1 over USB serial Experimental; implemented and mock-tested
B1 Pro, B21 Pro, D11, D11_H, D110 Experimental monochrome profiles
B18 Geometry known; print task unresolved

Printing with any profile/task/connection combination other than B1+B1 over BLE requires --allow-experimental; offline previews and saved renders do not. Multi-colour printheads and colour raster protocols are out of scope.

Building from source requires Rust 1.98 or newer. rust-toolchain.toml tracks the current stable toolchain for rustup users.

Build and set up

Build from GitHub with Cargo:

cargo install --git https://github.com/kahwee/thermark --locked
# BLE only:
cargo install --git https://github.com/kahwee/thermark --locked --no-default-features --features ble

The first crates.io release is prepared but not yet published. Until then, use the commands above or a prebuilt binary.

For a prebuilt binary, download the latest release. Choose ble for the hardware-tested B1-over-Bluetooth path, or full to also include experimental USB serial support. Builds are available for Linux x86_64/ARM64 and macOS Apple Silicon/Intel; verification and unpacking commands are below. Building from source requires Rust:

scripts/build.sh

# Quit the vendor app first; only one BLE client can hold the printer.
./target/release/thermark scan --save
./target/release/thermark identify
./target/release/thermark doctor --use-config

When asking for setup help, create a support report that is safe to attach to a public issue:

./target/release/thermark doctor --use-config --json > thermark-report.json

The JSON includes the operating system, architecture, enabled transports, and check results. Printer names and addresses, serial numbers, RFID barcodes, and local filesystem paths are omitted.

For the smallest primary-path binary, omit USB serial support and build BLE only:

scripts/build.sh --no-default-features --features ble

The build script and local scripts/check.sh reset target/ on the first run after 14 days, then regenerate the artifacts needed by that command. Between resets, builds reuse the cache normally. The first use starts the retention clock for existing artifacts. Cleanup removes generated binaries, debug/release caches, and diagnostics under target/; source files, local/, and printer settings are unaffected. Plain cargo build does not run this cleanup. CI retains its own cache policy.

Releases include SHA-256 checksums. Verify the checksum before unpacking, for example (replace the version and platform with your download):

# Linux
sha256sum --check thermark-0.33.0-Linux-X64-ble.tar.gz.sha256

# macOS
shasum -a 256 --check thermark-0.33.0-macOS-ARM64-ble.tar.gz.sha256

tar -xzf thermark-0.33.0-macOS-ARM64-ble.tar.gz
cd thermark-0.33.0-macOS-ARM64-ble
./thermark --version
./thermark tasks

Contributors can run the same validation entry point locally and in CI:

scripts/check.sh           # formatting, Clippy, default-feature tests and docs
scripts/check.sh all       # also lint/test/document each transport feature set
scripts/check.sh render    # focused golden / fixture / placement checks

Use scripts/check.sh --help for prerequisites and modes. Checks require no printer; failed golden renders appear in target/golden-actual/.

To capture the exact model, firmware, geometry, and task for a hardware report:

./target/release/thermark identify --json \
  > local/printer-identity.json

Use the full advertising name shown by scan. Matching is exact by default; --fuzzy enables intentional substring matching. On macOS, Bluetooth device IDs are UUIDs rather than classic MAC addresses.

macOS Bluetooth ownership

macOS can show a printer as Connected while thermark reports that it is not discoverable. One likely cause is that another Bluetooth client—often a vendor app or a system-managed session—still owns this printer's BLE GATT connection. The same symptom can also mean the printer is asleep, out of range, or not advertising, so treat ownership as a diagnosis to verify rather than a fact.

Before retrying, disconnect the printer in macOS Bluetooth settings, quit any vendor label app, wake or power-cycle the printer, and then run:

./target/release/thermark scan
./target/release/thermark doctor --use-config

If doctor reports a matching /dev/cu.… endpoint, that supports the ownership diagnosis, but proves neither ownership nor serial-protocol compatibility. Use the endpoint with --conn usb only when the printer responds to the serial protocol; otherwise release any competing Bluetooth session and use BLE normally.

Print stickers

Always pass the physical label size. After scan --save, the printer address is read from config.

Guest Wi-Fi:

THERMARK_WIFI_PASSWORD='your-password' \
  ./target/release/thermark wifi \
  --ssid "YourNetwork" \
  --label 50x30

Open networks do not need a password:

./target/release/thermark wifi \
  --ssid "Cafe-Guest" \
  --security nopass \
  --label 50x30

URL with readable text:

./target/release/thermark qr \
  --url "https://example.com/o/1042" \
  --text $'ORDER #1042\nPriority' \
  --font-name helvetica \
  --label 50x30

Plain text:

./target/release/thermark text \
  --text $'FRAGILE\nthis way up' \
  --label 50x30

Existing artwork:

./target/release/thermark print \
  -i local/prints/art.png \
  --label 50x30 \
  --no-fill \
  --margin 0 \
  -d 4

Personal artwork and real credentials belong under local/, which is ignored by git. Committed files under fixtures/ contain public demo data only.

Which printer profile controls rendering

For an online text, qr, wifi, or calibrate command, thermark connects and identifies the printer before converting the physical label size to pixels. The detected profile's DPI and printhead width therefore determine the rendered canvas. If a generated-label command also uses --save, the PNG is written from that same detected-profile render before it is printed.

Hardware printing fails closed if the identity probe fails or reports an unrecognized model. thermark will not render or send a job using provisional profile geometry in that case.

--no-print remains connection-free. In that mode, generated labels use the profile selected by --model, then the saved configuration, then the B1 default. Combine --save <path> --no-print when you want a wholly offline render.

Online raw-image printing also lays out the image against the detected profile. Its default 1 mm registration margin is converted at the detected DPI; an explicitly saved pixel inset and --full-bleed remain exact.

Preview and calibrate

Preview the exact bitmap for the selected profile without printing:

./target/release/thermark print \
  -i local/prints/art.png \
  --label 50x30 \
  --preview local/preview.png

print --preview applies the selected threshold and dithering and writes the final monochrome page for the configured or --model profile. If that profile does not match hardware later detected by an online print, its geometry can differ. Generated sticker commands use --save <path> --no-print to inspect their composed artwork; those saved PNGs can retain antialiasing that the printer later thresholds. Without --no-print, --save waits until printer identification so the saved bitmap and printed page share the same profile-sized render.

Check label placement on hardware:

./target/release/thermark calibrate --label 50x30
./target/release/thermark calibrate --boundary --label 50x30

Label size and RFID

The printer's RFID response can describe the consumable type and remaining label count, but it does not report dimensions in millimetres. Vendor software may resolve an RFID barcode through its own catalogue; thermark has no cloud catalogue. Pass --label WxH when media changes, or save the size with thermark config set --label WxH.

If a print looks wrong

  1. Run thermark info and charge the printer if the battery is low.
  2. Preview the bitmap to separate rendering problems from hardware problems.
  3. Print the same bitmap twice. Inconsistent truncation points indicate power, not geometry.
  4. Use calibrate --boundary only after the printer is charged.

A charged B1 reaches the full feed canvas. Its default 1 mm top/bottom inset is registration margin, not a known unprintable band.

For connection failures on macOS, follow the Bluetooth ownership checks above.

Development

scripts/check.sh all       # formatting, Clippy, tests, and docs across feature sets
scripts/check.sh render    # golden, fixture, and placement checks
cargo bench --locked --bench image_pipeline
cargo bench --locked --no-default-features --bench packet_decoder
PROPTEST_CASES=4096 cargo test --locked --no-default-features --test packet_stream

Linux BLE builds require libdbus-1-dev and pkg-config. The shared check script uses locked dependencies, rejects accidental golden-image updates, and matches the validation run by CI on Linux and macOS. Packet-stream property tests cover arbitrary fragmentation, full-size payloads, corruption recovery, and bounded retained input. Commit any generated proptest-regressions/ seeds when fixing a failing property so the same input stays covered.

Dependency security runs separately on dependency changes and weekly against the current RustSec advisory database, failing on vulnerabilities, unsoundness advisories, and yanked crates. Unmaintained notices remain visible: ab_glyph currently pulls in ttf-parser, covered by RUSTSEC-2026-0192. There is no patched upstream version; replacing the font engine needs dedicated rendering verification. To reproduce it locally:

cargo install cargo-audit --version 0.22.2 --locked
cargo audit --deny unsound --deny yanked

The benchmarks report CPU-only medians. Compare runs on the same host, and measure peak RSS in separate processes when evaluating memory changes. See the packet decoder measurements for the bulk-read optimization and its small-read tradeoff.

The architecture keeps four concerns separate:

  • profile.rs: detected printer identity, physical capabilities, default task, and the single support-status registry exposed as PROFILES.
  • transport/: BLE and USB communication.
  • printer/: validated job lifecycle and protocol queries.
  • label.rs / image_encode.rs: layout and monochrome raster generation.

For library API migrations, see the explicit old-to-new mappings under 0.32.0 → Removed.

See AGENTS.md for protocol details, hardware measurements, and contributor invariants.

License

MIT

Help support more printers

Own a B1 Pro, B21 Pro, D11, D11_H, or D110? A physical test with public demo content helps more than a successful compilation. Share your model, firmware, OS, connection, label size, privacy-safe diagnostics, and a photo of the result through the hardware report form.

Read CONTRIBUTING.md for a short testing checklist. Support status changes only when the evidence supports it.