For developers & ISVs

Integrate restaurant hardware once.

Every POS team ends up owning a private zoo of printer drivers, certified-hardware whitelists, and 'printer offline' tickets. Proxy Nodes puts that whole problem behind one HTTP/JSON API on the restaurant's LAN — printer, cash-drawer pulse, and barcode scanner behind the same contract. A byte-compatible simulator and 123 conformance specs mean the integration passes in CI before a printer is on your bench; the same specs then run unchanged against real hardware.

PN-PROTO-1 concept render — a palm-size two-tone node with ethernet, printer and scanner USB ports, USB-C power, and a status LEDPN-PROTO-1 · concept

mDNS at proxynodes-<id>.local · JSON on :80 · raw ESC/POS on :9100

The API

One call. The node does the last mile.

Nodes announce themselves over mDNS and serve JSON on the LAN. Your software never touches ESC/POS, RS-232 framing, or USB descriptors — unless it wants to.

Print a receipt

curl http://proxynodes-7f3a.local/print \
  -X POST -H "content-type: application/json" \
  -d '{
    "lines": [
      { "type": "banner", "value": "ORDER", "align": "center" },
      { "type": "text", "value": "Table 4", "bold": true, "size": "2x" },
      { "type": "barcode", "symbology": "qr", "value": "ORDER-1042" },
      { "type": "cut" }
    ]
  }'

Seven line types: text (with bold, alignment, and double width or height), banner block lettering the node draws itself, rules, feeds, cuts, barcodes, and 1-bit rasters. The node wraps text at the paper’s real column count, so a line that will not fit comes back as an error rather than as a receipt missing its right-hand half.

Ask how the hardware actually is

curl http://proxynodes-7f3a.local/status

{
  "identity": {
    "deviceId": "pn-7f3a", "variant": "wifi",
    "firmwareVersion": "1.0.24"
  },
  "online": true,
  "uptimeSeconds": 86213,
  "printers": [{
    "type": "printer_status", "endpoint": "receipt",
    "online": true, "paperOut": false, "coverOpen": false,
    "drawerOpen": false
  }]
}

Live paper, cover, and drawer state, read over DLE EOT. And when a reading can't be taken — a write-only printer, a dead status line — the driver layer models it as known: falserather than inventing "no faults", so "paper is fine" and "I can't tell" never get conflated.

Already speak :9100?

If your software prints raw ESC/POS to network printers today, there is no integration at all: point it at the node's :9100 port. DLE EOT status queries are answered too.

Swap hardware, not code

Star to Epson to a $60 generic is a node-side config change. Your software keeps calling the same API.

Tune per printer, remotely

Cut clearance, print width, USB pacing — runtime config over PUT /config, persisted on the node. No reflash, no site visit.

Nodes also phone home on a schedule — a heartbeat, not a control channel — configured over GET/PUT /cloud. Point it at your own server instead of the Proxy Nodes service; see the self-hosted telemetry guide for the wire contract.

Under the hood

One classifier, five verdicts. Rejection is never silent.

Every USB device that appears gets classified, and every branch that turns a device away records why — VID:PID and make/model are logged before any rejection, so a refused device is still nameable by whoever is standing in front of it.

USB plug-in

Device enumerates · VID:PID + make/model recorded

pn_classify_device()

Printer

BOUND in 1,012 ms

Bulk-OUT for bytes, bulk-IN for DLE EOT status. Ready to print.

Scanner

BOUND · HID boot

Reports framed into barcodes; every read logged with a verdict.

Hub

Enumerate children

Powered hubs supported — printer + scanner behind one, simultaneously.

Scale

In development

Recognized and named, not yet driven. Reported honestly in /status.

Unknown

Rejected, with reason

VID:PID and model kept — the refusal names the device it refused.

A STALL and a silence are different: a stall completes the transfer and the device stays bound — one job lost, not the printer. A silence marks it not-ready. Every rejection names the reason GET /status will report.

Scanners

Every read gets a verdict.

A keyboard-wedge scanner types whatever it decodes and never explains itself. The node keeps the record it doesn't.

curl http://proxynodes-7f3a.local/scans

{
  "endpoint": "scanner",
  "accepted": 214, "rejected": 3, "capacity": 32,
  "scans": [
    { "value": "0123456789012", "verdict": "accepted",
      "symbology": "ean13", "symbologySource": "aim",
      "ageMs": 1180, "gapMs": 2 },
    { "value": "0123456789012", "verdict": "duplicate",
      "symbology": "code128", "symbologySource": "inferred",
      "ageMs": 5411, "gapMs": 640 }
  ]
}

GET /scans is a ring of recent reads with a verdict on each. Reads the node refused stay in the log with the reason — duplicate, too_short, burst — because a rule that silently drops a barcode is indistinguishable from a broken scanner.

Symbology is labeled by provenance: aim means the scanner transmitted it, inferred means the node worked it out from the payload — and an inference stays marked, so your software never routes on a guess without knowing it. Decode policy — read modes, timeouts, symbology allow-lists, keyboard layouts — is set over PUT /config, same as printer tuning.

And nodes find each other: GET /peers lists every other node heard on the LAN, so one known IP reaches the whole fleet.

CI-first

Your integration passes before a printer exists.

The digital twin is a LAN service that mirrors the firmware byte for byte — same API, same :9100 behavior, same telemetry — with fault injection for the failure paths you can't schedule on real hardware.

$ pnpm sim                 # digital twin: HTTP :8080, :9100, mDNS
$ pnpm print               # POST a sample receipt — renders in the console
$ pnpm conformance         # protocol conformance suite vs the twin

sim> paper out             # now make it a bad day
sim> cover open
sim> offline

The conformance suite is the honest answer to "does passing against a simulator mean anything?" — one suite, validated against shared schemas, that runs identically against the twin and a physical node on your bench.

Protocol conformance is unconditional; physical-effect assertions skip with a printed reason when the hardware isn't attached — never a silent pass.

More on the simulator →

123 conformance specs · 996 JS tests · 32 firmware suites under ASan/UBSan

Guides

The reference material we wished existed.

Deep technical guides on the protocols and failure modes of receipt printing — useful whether or not you ever run a node.

All guides →