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:
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:
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
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 |
|---|---|---|
![]() |
![]() |
![]() |
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:
# BLE only:
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:
# Quit the vendor app first; only one BLE client can hold the printer.
When asking for setup help, create a support report that is safe to attach to a public issue:
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:
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
# macOS
Contributors can run the same validation entry point locally and in CI:
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:
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:
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' \
Open networks do not need a password:
URL with readable text:
Plain text:
Existing artwork:
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:
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:
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
- Run
thermark infoand charge the printer if the battery is low. - Preview the bitmap to separate rendering problems from hardware problems.
- Print the same bitmap twice. Inconsistent truncation points indicate power, not geometry.
- Use
calibrate --boundaryonly 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
PROPTEST_CASES=4096
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:
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 asPROFILES.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.


