# Brother P-touch PT-P950NW label printing for BEC: design plan > **Status: proposal, not yet implemented.** This document captures the plan > from an initial feasibility/design session so it isn't lost; it should be > updated (or removed / folded into an architecture note) once the feature is > actually built. ## Context When a measured sample is unloaded from the flomni sample tray, the only record of "who owns it / when it was measured" today is digital (tray-slot metadata in `dev.flomni_samples`, plus the OMNY sample database via `TomoIDManager`). There is no physical label on the actual sample holder, so a user picking it up later has no way to tell at a glance whose sample it is or when it was run. The goal of this feature is to print a small paper label (account + date, on TZ tape) at the moment a measured sample is stowed back in the tray, so it can be stuck straight onto the sample holder. This is explicitly **not** an experiment hardware component (no motion, no staging, nothing that needs to be part of a scan) — it's a side-effect utility, similar in spirit to the existing `TomoIDManager` (HTTP registration call) and `HttpUploader` (webpage push) helpers, which are plain Python classes invoked from the flomni/lamni plugin code at well-defined workflow points, not ophyd devices. **Decisions already made:** - Protocol: **P-touch Template mode** (Brother's official, documented command protocol) rather than the raster/reverse-engineered protocol used by third-party libraries like `rasterprynt`. This avoids needing an image library (Pillow) client-side and avoids depending on an undocumented protocol that could break on a firmware update — fits the goal of minimizing external packages. - Scope: build as a **shared utility** in `bec_ipython_client/plugins/OMNY_shared/`, so lamni/omny can adopt it later without duplicating printer/network code — mirrors where `TomoIDManager` and `HttpUploader` already live. - Trigger: **both**. Automatic prompt-then-print on sample unmount in flomni, plus a manual/standalone command for on-demand (re-)printing. ## How P-touch Template mode works (research summary) - The PT-P950NW supports raw network printing on **TCP port 9100** (listed in its spec sheet alongside LPR/IPP/mDNS etc.), the same "JetDirect-style" raw port most network printers use — no CUPS/driver install needed. - "P-touch Template" is a printer-side mode built for exactly this use case (the manual explicitly lists "data from a scale, testing machine, controller, or PLC" as the target): you design a label layout **once** in Brother's Windows-only **P-touch Editor** (place named text objects, e.g. `ACCOUNT`, `DATE`), transfer that template onto the printer's internal memory (via Editor's Transfer Manager over USB or network), and from then on printing is just: connect to the printer, send a short **ESC i X** command sequence selecting the template number and supplying the text for each named field, then a print-start command. - The command set is documented in Brother's "P-touch Template Command Reference" (per-printer-family PDF from Brother's support site — confirm the PT-P900W/P950NW-specific revision before implementing, since control codes can differ slightly between printer families/firmware generations). - No actively-maintained Python package implements this specific protocol (unlike raster printing, which has `rasterprynt`/`brother_ql`-style libraries). That's fine here: the runtime protocol is just building a byte string and pushing it over a `socket` — stdlib only, no new dependency. ## Implementation plan ### 1. New shared module: `PTouchLabelPrinter` File: `csaxs_bec/bec_ipython_client/plugins/OMNY_shared/ptouch_printer.py` (new file, alongside `omny_general_tools.py`/`web_common.py`). ```python class PTouchLabelPrinter: """Prints a label on a Brother PT-P950NW via P-touch Template mode.""" DEFAULT_PORT = 9100 def __init__(self, host: str, template_number: int = 1, port: int = DEFAULT_PORT, timeout: float = 3.0): ... def print_label(self, fields: dict[str, str], copies: int = 1) -> bool: """Build the P-touch Template command for `fields` and send it. Returns True on success, False on any network/printer error -- never raises, matching TomoIDManager's fail-soft convention so a printer being offline can never block a sample transfer. """ ... ``` - Builds the ESC-prefixed command sequence (select template mode, select template number, set each named field's text, set copy count, print command) as pure bytes, opens a short-lived `socket.create_connection((host, port), timeout=timeout)`, sends, closes. - Wrap all I/O in `try/except`, `logger.warning(...)` on failure, return `False` — same fail-soft style as `TomoIDManager.register()` (`omny_general_tools.py:396-`) and `HttpUploader` (`web_common.py:48-114`). - Printer host/IP and template number are constructor args (not hardcoded constants), passed in from each plugin's config — check how other beamline-specific endpoints are configured in this repo (e.g. is there a per-instrument config file, or are they class constants like `TomoIDManager.OMNY_URL`?) and follow that convention when wiring it up. ### 2. Wire into flomni File: `csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py` - Instantiate once in `__init__`, next to `self.OMNYTools = OMNYTools(...)` (line ~1654) and `self.tomo_id_manager = TomoIDManager()`: `self.label_printer = PTouchLabelPrinter(host=, ...)`. - **Automatic path**: in the unmount branch of `ftransfer_sample_change` (the `new_sample_position == -1` case, `flomni.py:867-906`) or in `ftransfer_get_sample` (`flomni.py:734`, name/owner already unpacked at line 786-787 as `name, owner = unpack_desc(signal_name.get())`), after the stow completes: ask `self.OMNYTools.yesno("Print a label for this sample?", "y")` (same helper already used elsewhere, e.g. `flomni.py:2507`) and on `True` call `self.label_printer.print_label({"ACCOUNT": owner, "DATE": str(datetime.date.today())})`. - **Manual path**: add a small method, e.g. `fprint_sample_label(position: int)`, that reads name/owner via the same `unpack_desc(...)` pattern as `sample_get_name` (`flomni.py:853`) and calls `label_printer.print_label` directly — usable standalone for reprints, without going through a transfer. ### 3. Open items to resolve before/during implementation - **No date is currently stored per sample slot** — `pack_desc`/`unpack_desc` (`devices/omny/sample_desc_codec.py:19,40`) only carry `(name, owner)`, no timestamp. Using `datetime.date.today()` at unmount time is a reasonable v1 approximation (unmount typically happens same-day as the scan), but if an exact measurement date is wanted later, the DESC codec would need a third field. - **Confirm the exact model-specific command reference.** Brother publishes a separate "P-touch Template Command Reference" per printer family; download the PT-P900W/P950NW-specific PDF from Brother's support site before writing the byte-level command builder. - **Template design is a one-time, Windows-only step** (P-touch Editor + Transfer Manager) — not scriptable from the Linux beamline control host. Worth doing once per label layout/TZ tape width; layout changes require redoing this step. - **Network reachability**: confirm the printer's IP is reachable from wherever `bec_ipython_client` runs (same VLAN/firewall rules as other network endpoints it already calls, e.g. the OMNY/tomo-ID server) and assign it a static/reserved IP. - Extend to lamni/omny by instantiating `PTouchLabelPrinter` in those plugins too once flomni is validated — no code changes needed to the shared module itself. ## Verification 1. **Bench test the printer/template alone**: install the PT-P950NW on the beamline network with a fixed IP, design the label template once in P-touch Editor (fields `ACCOUNT`/`DATE`, chosen TZ tape width), load it via Transfer Manager, and print a manual test label from the desktop app to confirm the template renders as expected. 2. **Bench test the protocol module standalone**: write `PTouchLabelPrinter`, then from a scratch script call `print_label({"ACCOUNT": "e12345", "DATE": "2026-07-31"})` against the real printer IP and confirm a correct physical label comes out (check encoding, field placement, tape length/cut). 3. **Integration test in flomni**: in a real or simulated `bec_ipython_client` session, run `ftransfer_sample_change(-1)` (or `ftransfer_get_sample`) end-to-end, confirm the yes/no prompt appears, confirm printing on "y", confirm declining or a deliberately-unreachable printer IP does **not** raise or block the sample transfer (only logs a warning). 4. Test the manual `fprint_sample_label(position)` command independently of any transfer, for reprints.