Read protocol.md first if you want to use TiMini-Print from your own code. Read catalog.md for profile/model JSON data. This document is about package boundaries and where code belongs.
The app-level flow is:
devicesresolves aPrinterDevicetransportopens a connector-specific connectionprinting.connected.connect_printer(...)prepares runtime stateConnectedPrinterprints files/text or sends prepared jobsprotocolbuilds packet payloads and optional protocol stepstransportwrites bytes and exposes generic query/wait primitives
The important object at runtime is PrinterDevice. It is the shared description used by protocol, printing, and transport without making those packages own each other.
Owns printer description and catalog resolution.
It contains PrinterDevice, model/profile data classes, PrinterCatalog, config serialization, Bluetooth endpoint models, BLE transport profiles, and BluetoothEndpointResolver. It may decide which logical printer a raw endpoint represents and select the ready-to-use BLE profile for it. It must not perform I/O.
Owns the file-to-job flow and stateful print-session behavior.
It contains ConnectedPrinter, connect_printer, PrintJobBuilder, DocumentRenderer, PrintSettings, send helpers, and runtime controllers. This package is allowed to coordinate devices, protocol, rendering, and transport because it is the app-level print orchestration layer.
Owns stateless wire-format construction.
It contains PrinterProtocol, ProtocolJob, protocol families, packet builders, image encoding choices, paper-mode recipe values, and internal low-level builders. It must not connect to hardware, scan Bluetooth, or know transport adapters.
Owns files, pages, converters, transforms, and rasterization.
It should not know printer protocols. printing.DocumentRenderer bridges rendering output into printer-specific job building because that step needs printer settings and selected paper/image pipeline.
Owns shared raster types.
It exists so rendering and protocol can share RasterBuffer, RasterSet, and pixel formats without importing each other.
Owns actual I/O.
It contains connector interfaces, connection implementations, Bluetooth adapters, and serial transport code. Transport may expose generic send/query/wait primitives. Bluetooth adapters receive a selected BLE profile and apply its GATT endpoints, chunk limits, and pacing; they do not select behavior from a protocol family. Transport must not contain printer-family opcode logic or firmware-state decisions.
A resolved printer instance as the program intends to use it. It combines display name, profile, protocol family, protocol variant, image pipeline, runtime settings, paper presets, optional transport target, and the BLE transport profile derived by the devices layer.
The high-level object for an active printer session. It owns an active connection and prepared runtime context, then exposes print_file(...), print_text(...), send_job(...), feed(), retract(), and disconnect().
CLI and GUI should use ConnectedPrinter instead of manually combining PrintJobBuilder, runtime preparation, and send_prepared_job.
A lower-level file-to-job builder. It turns files into ProtocolJob objects using DocumentRenderer and PrinterProtocol. It does not own connection lifetime or runtime preparation.
Use it directly for preview/debug/streaming-page workflows where a caller wants jobs without immediately printing them.
A protocol builder bound to one PrinterDevice. It builds ProtocolJob from raster input and may produce named protocol steps for families that need interleaved send/query/wait operations.
Internal family builders return one ProtocolPlan shape for both stream-only and step-based protocols. The public ProtocolJob wraps that stateless plan with job-level execution policy.
It is not a connection object. Do not add Protocol(connector).send(...) style APIs.
A stateless protocol execution plan. It contains payload bytes, optional payload segments, optional named steps, and whether printing should wait for protocol completion. It does not contain a live runtime controller. Stream-only jobs can be sent directly by a connection; jobs with steps must go through ConnectedPrinter.send_job(...) or send_prepared_job(...).
Transport sees generic send/query/wait operations. It does not learn family-specific command meaning.
A connector connects using a resolved PrinterDevice and returns a connection. A connection can send a stream-only ProtocolJob and disconnect. Some connections also support the generic operations needed by the printing layer to execute step-based jobs, such as control-packet send/query, bulk send, and notification waits.
Most app-level code should pass a connector into connect_printer(...) and use the returned ConnectedPrinter.
Protocol and transport stay separate so these combinations remain possible:
- repo discovery + repo transport +
ConnectedPrinter - repo discovery + custom transport +
ConnectedPrinter - explicit
PrinterDevice+ repo transport +ConnectedPrinter - explicit
PrinterDevice+ custom transport +ConnectedPrinter PrinterProtocolonly, with no repo transport at all
Packet construction belongs in protocol. Connection mechanics belong in transport. Stateful protocol synchronization belongs in printing.runtime, because it sits between the packet plan and the live connection.
There are two kinds of protocol-related behavior:
- stateless packet building
- stateful session behavior
Stateless packet formats belong in timiniprint.protocol.families.*. Runtime behavior belongs in timiniprint.printing.runtime.* when it depends on current session state, notifications, timing, previous writes, firmware replies, or completion waits.
prepare_connection_runtime(...) selects a runtime controller for the resolved PrinterDevice. If no controller is needed, it returns an empty context. If a controller is needed, it may attach to the connection, probe capabilities, run a handshake, or prepare notification state.
GATT write response is not a printer protocol ACK. If a family needs ACKs, status, or completion waits, model that as protocol steps and runtime controller behavior, not as transport adapter policy.
Catalog detection and Bluetooth discovery are different concerns.
PrinterCatalog.detect_device(...) does not scan hardware. It maps a known name/address to a printable PrinterDevice when the catalog match is unambiguous.
BluetoothDiscovery scans hardware, asks BluetoothEndpointResolver to merge raw endpoints, then returns PrinterDevice objects for devices that can be printed automatically. UI/CLI scan views may use display helpers to include ambiguous or unsupported manual candidates.
Transport owns scanning mechanics. Devices own turning raw endpoints into logical printers.
User-facing paper choices are catalog data. Profiles list exact paper preset keys; PrintSettings.paper_preset_key selects one for file printing.
Rendering uses the preset's render width. Protocol families receive low-level values they understand, such as left padding, maximum sheet height, or paper_mode. Transport does not receive media data.
paper_mode is a protocol recipe value. It must not become the GUI/CLI data source for paper selection.
Detailed paper preset data rules are in catalog.md.
Allowed direction is:
rendering -> rasterdevices -> protocol.family|protocol.typesprotocol -> rasterprinting -> devicesprinting -> renderingprinting -> protocolprinting -> transporttransport -> devicestransport -> protocol
Practical rules:
- rendering should not depend on protocol builders
- protocol should not depend on transport
- protocol should not depend on printing runtime controllers
- devices should describe printers, not perform I/O
- transport should not know printer opcodes or family-specific ACK semantics
Put it in devices if it changes printer description, model detection, endpoint merging, profile loading, or config serialization.
Put it in rendering if it changes how files become pages or raster data.
Put it in protocol if it changes stateless packet building, compression, encoding, protocol variants, or command payloads.
Put it in printing if it changes file-to-job orchestration, print settings, diagnostics, connected-session behavior, send sequencing, or runtime controllers.
Put it in transport if it changes actual connection, scanning backend mechanics, characteristic selection, chunk writes, serial writes, or generic query/wait primitives.
If a change requires protocol-specific timing, ACK handling, or notification interpretation, it belongs in printing.runtime or protocol steps, not transport adapters.