2026-07-09 10:57:37 +02:00
2026-07-09 10:57:37 +02:00
2026-07-09 10:57:37 +02:00
2026-07-09 10:57:37 +02:00
2026-07-09 10:57:37 +02:00
2026-07-09 10:57:37 +02:00
2026-07-09 10:57:37 +02:00
2026-07-09 10:57:37 +02:00

escpos-proxy

Minimal ESC/POS print proxy written as a Node.js ESM app.

It listens for incoming ESC/POS print jobs on a TCP port, forwards the raw bytes straight to a target printer, and logs every job to disk as both a raw .bin file and a decoded .txt sidecar.

A job is delimited by either:

  • a cut command ("bladecut") in the stream — ESC/POS GS V = 0x1D 0x56, or
  • a connection drop (the client closes / resets the connection).

No database, websocket, or receipt re-rendering is involved.

Usage

Configuration lives in .env (copy .env.example to .env and edit). It is loaded via dotenv.

cp .env.example .env   # first time only
npm start

Environment variables (.env)

Variable Default Description
LISTEN_HOST 0.0.0.0 Host the proxy listens on
LISTEN_PORT 9100 Port the proxy listens on
TARGET_HOST 10.10.10.24 Printer to forward raw bytes to
TARGET_PORT 9100 Printer port
LOG_DIR ./logs Directory for job logs
CUT_PREFIX 1d 56 ESC/POS cut command prefix, hex space-separated (GS V)
KEEP_AFTER_CUT true Treat bytes after a cut as the next job

Real environment variables always take precedence over .env.

Logs

For each completed job, two files are written into LOG_DIR:

  • job-<timestamp>-<seq>.bin — the exact raw bytes (reprintable).
  • job-<timestamp>-<seq>.txt — the same bytes decoded as latin1 (ASCII + control characters visible, ESC/POS faithful).

Testing

test-send.mjs sends a sample ESC/POS receipt (with a GS V cut) to the listener so you can verify logging and forwarding:

# terminal 1
npm start

# terminal 2
npm run test:send

Parsing the logs

parser.js is a lossless ESC/POS parser. It decomposes the raw byte stream into the ESC/POS command grammar: each control sequence is recognized by its opcode with its parsed parameters, and text is kept as raw byte spans (decoded via the selected code page). Nothing is interpreted into higher-level meaning (no "this is bold", no line/style inference) — it is a faithful tokenization. Every byte is preserved, and the original stream can be rebuilt via serialize(parse(buf)).

Token types:

  • { type: "text", bytes, text } — raw bytes + code-page decode
  • { type: "command", bytes, group, name, params } — control sequence + params
  • { type: "image", bytes, width, height, data } — raster bit image (GS v 0)

parse-log.mjs is a CLI wrapper. By default it prints the parsed token stream (opcode + command name + parameters, text spans, images) and verifies the round-trip serialize(parse(buf)) === buf (no bytes lost).

# parsed token stream (default) — a faithful decomposition
npm run parse -- logs/job-2026-07-08T21-08-02-523Z-0003.bin

# parse every .bin in a directory
node parse-log.mjs logs --all

# also show the raw bytes of each token
node parse-log.mjs logs/job-...bin --hex

# convenience: decoded receipt text only (drops structure — not lossless)
node parse-log.mjs logs/job-...bin --text

# also render raster bit images (e.g. QR codes) as ASCII art
node parse-log.mjs logs/job-...bin --image --max-width 60

As a module:

import { parse, serialize, renderText } from "./parser.js";
const buf = fs.readFileSync("logs/job-xxx.bin");
const events = parse(buf);                  // lossless token stream
console.log(serialize(events).equals(buf)); // true — nothing lost
Description
No description provided
Readme 71 KiB
Languages
JavaScript 100%