letratag-protocol
Bounded, offline wire-protocol encoding for the DYMO LetraTag 200B thermal label printer. No Bluetooth, CUPS, or hardware dependency: this crate turns a 32-row raster into the framed characteristic writes a BLE transport sends, and parses the printer's status notifications.
Part of letratag-linux.
Install
[]
= "0.1.1"
Rust 1.98 or newer. No default features; there are no feature flags.
Examples
Raster model and column packing
A Raster is a full 32-row frame (HEAD_ROWS), row-major, containing only 0
(white) or 1 (black). x is the tape travel (feed) direction; y = 0..31
runs across the tape.
use ;
let mut pixels = vec!;
pixels = 1; // column 0, row 0
pixels = 1; // column 1, row 31
let raster = new.unwrap;
assert!;
assert!;
assert!;
// Wire packing: column x occupies bytes 4x..4x+4; row 0 is bit 7 of the
// last byte, row 31 is bit 0 of the first byte.
assert_eq!;
assert_eq!;
Encoding a print job into bounded writes
Job::writes(max_write) returns the ordered characteristic values: a 9-byte
header first, then indexed body chunks. max_write is the usable
characteristic value length, NOT the raw ATT MTU. Every chunk reserves one
index byte and the final chunk two magic bytes, so the body ceiling is
min(500, max_write - 3). At chunk position 27 the index 27 is skipped
(i >= 27 sends i + 1), and at most 255 body chunks are produced.
use ;
let raster = new.unwrap;
let job = new.unwrap;
// Modern body: 28 bytes of framing plus 4 bytes per column.
assert_eq!;
let writes = job.writes.unwrap;
assert_eq!; // header
assert!;
let last = writes.last.unwrap;
assert_eq!; // final magic
Header and status parsing
use ;
let raster = new.unwrap;
let job = new.unwrap;
let writes = job.writes.unwrap;
assert_eq!;
// Only code 0 is recognized as success; every other code is preserved
// unchanged as Unconfirmed.
assert_eq!;
assert_eq!;
assert!;
Set-cassette-type short command
set_cassette_type_write builds one 23-byte non-print write for the
auxiliary characteristic (HEADER + ESC s + ESC M <id> 00 00 00 + ESC Q).
The cassette id enum is 1=6mm, 2=9mm, 3=12mm, 4=19mm, 5=24mm; the LT-200B
chassis only accepts 12 mm (CASSETTE_ID_12MM).
use ;
let write = set_cassette_type_write.unwrap;
assert_eq!;
assert!;
assert!;
API overview
| Item | Purpose |
|---|---|
Raster |
Validated 32-row binary frame: new, width, pixels, get, pack_columns, from_packed_columns |
Profile |
Wire profile: Modern (default, S2-derived) or Legacy (S1-derived) |
Job |
One encoded print job: new, body, writes |
parse_header |
Validate header framing/checksum, return the bounded body length |
Status |
Success / Unconfirmed(u8) status notifications: parse |
set_cassette_type_write |
Stand-alone set-cassette-type write for the auxiliary characteristic |
HEAD_ROWS, MAX_COLUMNS |
Frame height (32) and software width limit (8000 columns) |
CASSETTE_ID_12MM |
Cassette id 3, the only width the LT-200B chassis accepts |
Error |
Bounded, structured encoding/parsing errors |
Limitations
- This crate encodes a wire format; it does not print. Transmitting writes to
a printer is the job of a transport (see
letratag-transport). - The legacy profile is hardware-verified on one LT200B firmware
(
RUTEP20220628); the modern profile is source-backed, not hardware-confirmed. The implementation keeps both profiles explicit and never switches automatically. - A status code 0 is not proof of a physical label: the printer reports success even with the lid open (phantom-success finding in the main repository). Completion policy belongs to the consumer.
MAX_COLUMNS(8000) is a software limit, not a guaranteed device capability. A job that needs more than 255 chunks is rejected before any write.- The literals and framing are transcribed from two MIT-licensed open-source
references (ysfchn/dymo-bluetooth, thermal-label/letratag); see the main
repository's
docs/protocol.mdfor sources and evidence levels.
License
MIT. See the main repository
for the full license text and the reference-notice inventory in
docs/legal/.