This commit is contained in:
seb
2026-07-09 10:57:37 +02:00
commit 9466d295a4
7 changed files with 855 additions and 0 deletions

102
README.md Normal file
View File

@@ -0,0 +1,102 @@
# 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](https://www.npmjs.com/package/dotenv).
```bash
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:
```bash
# 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).
```bash
# 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:
```js
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
```