Skip to main content

Printer Vendor Port

Source-backed guide for manufacturer resolution in packages/printer-transport/src/vendor-profile.ts.

Use this page when adding a printer brand or diagnosing why a saved printer stays generic. For merchant APIs and station claims, see Printer Config. For drawer opens, see Cash Drawer.


Intent​

Feature 052-mobile-generic-printing treated every printer as anonymous ESC/POS over TCP or BLE. Feature 053-mobile-vendor-printing adds a vendor profile registry: one code-defined table that says what each manufacturer supports, over which connections, on which platform.

Resolution is vendor first, connection second. A Star printer on Ethernet must go through Star's adapter, not a raw socket, because only the vendor path returns paper/cover state and a drawer kick that works on models where ESC p does not.

The registry lives in @flowpos-workspace/printer-transport and ships no adapters. Choosing a transport is the caller's job (apps/mobile/src/printing/transport-registry.ts on mobile; Print Bridge still implements PrinterTransport for TCP/USB).

Do not describe StarIO10 identifiers, USB permission dialogs, or HMAC hashing as domain behavior. Those are adapter or telemetry concerns.


Hexagonal map​

LayerCodepathsResponsibility
Domain (shared port)packages/printer-transport/src/vendor-profile.ts, transport.types.tsPrinterVendor, VendorEvidence, resolveVendor(), VENDOR_PROFILES
Applicationapps/backend/src/printer-config/application/vendor-resolution.service.tsBackfill saved rows from stored evidence; never from displayName
Infrastructureapps/mobile/src/printing/transports/*, apps/mobile/modules/printer-*StarIO10, Android SPP/USB, generic TCP/BLE
Interfacesprinter-config/manage DTOs, mobile handshake vendors fieldPersist the client's resolution; declare which SDKs this build loaded

Profiles in this release​

PrinterVendor is "generic" \| "star" \| "epson" \| "bixolon" \| "sunmi". generic is the fallthrough, never a match target (match: []).

VendorStatus reportingDrawer (model)iOS transportsAndroid transports
genericNoYes (ESC p)tcp, bletcp, ble, spp, usb
starYesYestcp, ble, usbtcp, ble, spp, usb
epsonYesYestcp, ble, usbtcp, ble, spp, usb
bixolonYesYestcp, bletcp, ble, spp, usb
sunmiYesYes—sunmi only

supportsCashDrawer on the profile means the model family can pulse a drawer. Whether one is wired to this printer is printer_config.supports_cash_drawer (default false).


What actually loads in the mobile container​

The registry can identify all five vendors. Adapters registered at startup (registerVendorTransports()) are narrower:

Adapter keyWhen it exists
generic:tcp, generic:bleAlways (feature 052)
generic:spp, generic:usbAndroid, when the Expo modules load
star:{tcp,ble,usb,spp}When react-native-star-io10 loads

There is no Epson, Bixolon, or Sunmi transport file in apps/mobile/src/printing/transports/ in this tree. Those printers still resolve and print on any generic connection they support (FR-012). sunmi has no generic fallback — transportFor({ connectionKind: "sunmi" }) throws until a Sunmi adapter registers.

A missing SDK is a first-class state, not a crash: Star adapters are lazy-required so Jest, Expo Go, and a build shipped without Star degrade to generic.

The container declares the intersection of VENDOR_PROFILES and loadedVendors() on the handshake (describeVendorIntegrations()). The backend does not keep a version→vendor map.


Resolution​

resolveVendor(evidence, source) is total and synchronous. Unmatched evidence returns { vendor: "generic" }. Order: sunmi, star, epson, bixolon.

Evidence (passive first)​

FieldTypical sourceNotes
usbVendorIdUSB scanAssigned IDs: Star 0x0519, Epson 0x04b8, Bixolon 0x0419 / 0x1504
macOuiARP, bonded device, BonjourFirst three octets; LAA bit 0x02 is cleared so Ethernet A4:D7:3C… and Bluetooth A6:D7:3C… match the same Epson
advertisedNameBLE / Bonjour / accessoryDevice-announced names only — never a merchant-typed displayName
bonjourTxtBonjour usb_MFG, tyPrinter-set
deviceIdResponseBounded probe (GS I 65/66/67)Only after passive resolution stays generic
hardwareManufacturerTerminal Build.MANUFACTURERResolves sunmi with no address

A probe may overwrite a passive result. Passive must never overwrite a probe (vendor_evidence on the row).

Saved-row backfill​

VendorResolutionService.evidenceFromRow rebuilds evidence from connection_kind, stable_identity (MAC-shaped only), and connection_target.vendorId. It skips vendor_locked_generic rows so a merchant who forced generic is not undone by a later sweep.

NULL vendor means "never resolved". 'generic' means "resolved, unrecognised". Collapsing those two would re-probe every no-name printer forever.


Adding a manufacturer​

Follow the existing extensibility test in packages/printer-transport/__tests__/vendor-extensibility.spec.ts:

  1. Add the vendor to PrinterVendor and a frozen VENDOR_PROFILES entry (match rules, transports, paper widths).
  2. Add a lazy-loaded adapter under apps/mobile/src/printing/transports/.
  3. Register it from registerVendorTransports() only when the native module is present.
  4. Do not edit discovery merge, station claims, the outbox, or the setup screen — they consume the handshake declaration.

Printed bytes stay receipt-escpos. Vendor SDKs are passthroughs (printRawData); they must not re-encode the slip.