Files
csaxs_bec/docs/developer/ptouch_label_printer_plan.md
T
x01dcandClaude Sonnet 5 251e228029
CI for csaxs_bec / test (push) Successful in 1m49s
docs(flomni): add design plan for Brother P-touch label printer integration
Feasibility/design session output: print account+date labels on
sample-tray unload via P-touch Template mode, as a shared OMNY_shared
utility rather than an ophyd device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGC1TfxZJvJHs6Qz1V8w1h
2026-07-31 16:03:25 +02:00

8.8 KiB

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).

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=<printer-ip>, ...).
  • 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 slotpack_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.