Skip to content

danielmeza/CrossEscPosEmulator

Β 
Β 

Repository files navigation

ESC/POS Receipt Printer Emulator

πŸ–¨οΈ This app emulates a networked receipt printer to test your ESC/POS commands against.

Emulator

About

  • Cross-platform application (Avalonia 12 + SkiaSharp + .NET 10), runs on Windows, macOS and Linux
  • Listens for ESC/POS commands over TCP/IP and (optionally) a serial port
  • Logs commands and visually represents the resulting receipt(s)
  • Renders 1D barcodes and 2D codes (QR, PDF417, DataMatrix, Aztec), bit images, and page mode
  • Answers status queries (DLE EOT, GS r, Automatic Status Back) β€” simulate paper-out, cover, drawer, offline and error states from the Printer state panel
  • Signals buzzer / cash-drawer events with a sound and on-screen toast
  • Configure the TCP listen address/port and serial port live from the UI
  • Export rendered tickets to PNG β€” all in one image, or one file per cut
  • It support different text formattings in the same line, although a few combinations were tested.

Cross-platform fork. This project began as a cross-platform port of roydejong/EscPosEmulator (originally a Windows/WPF app). It has been migrated to Avalonia + SkiaSharp + .NET 10 so it runs on Windows, macOS and Linux, and extended with barcode/QR rendering and a serial transport. All credit for the original emulator goes to the upstream author.

πŸ‘· This is an unfinished experiment. Use at your own risk and keep your expectations low. :)

Download

Pre-built, self-contained apps (no .NET install required) are published on the Releases page:

Platform Artifact
Windows (x64) CrossEscPos-win-x64.zip
Linux (x64) CrossEscPos-linux-x64.tar.gz
macOS (Intel) CrossEscPos-osx-x64.zip (.app bundle)
macOS (Apple Silicon) CrossEscPos-osx-arm64.zip (.app bundle)

Releases are produced by the Release GitHub Actions workflow on each v* tag.

macOS first launch. The .app is ad-hoc signed but not notarized (no paid Apple Developer ID). macOS quarantines anything downloaded from the internet, so on first launch you may see "CrossEscPos is damaged and can't be opened" (especially on Apple Silicon). Clear the quarantine flag once, then open it:

xattr -dr com.apple.quarantine /path/to/CrossEscPos.app
open /path/to/CrossEscPos.app

(Right-click β†’ Open also works once the quarantine is cleared.)

Built with

Supported commands

⚠️ Support is currently limited to only a subset of ESC/POS. Even the commands listed here may only be partially implemented.

  • Raw Text
  • LF: Line feed
  • CR: Carriage return
  • ESC Commands:
    • Initialize printer (ESC @)
    • Toggle italic (ESC 4 / ESC 5) [possibly deprecated?]
    • Select font (ESC M)
    • Select charset (ESC R)
    • Select character table (ESC t)
    • Select justification (ESC a)
    • Select line spacing (ESC 2 / ESC 3)
    • Toggle emphasis (ESC E)
    • Toggle underline (ESC -)
    • Set print text mode (ESC !)
    • Full cut (ESC m)
    • Partial cut (ESC i)
    • Print and feed n lines (ESC d)
    • Print and feed paper (ESC J)
    • Generate pulse / kick cash drawer (ESC p m t1 t2)
    • Select character code table (ESC t) β€” PC437/850/852/858/860/863/865/866/1252 remapped to Unicode
    • Beeper (ESC ( A)
    • Bit image (ESC *) β€” 8-dot and 24-dot inline raster
    • Page mode: select page / standard mode (ESC L / ESC S), print area (ESC W), direction (ESC T), absolute position (ESC $)
    • User-defined characters (ESC & / ESC % / ESC ?) β€” parsed & stored
  • Control characters:
    • Buzzer / beeper (BEL, 0x07)
    • Form feed (FF) β€” prints the page in page mode
    • Cancel (CAN) β€” cancels page data
  • DLE (real-time) Commands:
    • Real-time status (DLE EOT n, n=1-4)
    • Real-time request / recover (DLE ENQ)
    • Real-time cash-drawer pulse (DLE DC4 1 m t)
  • FS Commands:
    • Print stored logo (FS p n m)
    • Auto cut (FS } 0x60 n)
  • GS Commands:
    • Select character size
    • Select cut mode and cut paper
    • Paper eject (GS e n [m t])
    • Print raster image (GS v 0 [m xL xH yL yH ...pixels])
    • Print 1D barcode (GS k) β€” UPC-A/E, EAN-13/8, CODE39, CODE93, CODE128, ITF, CODABAR (both function A & B forms)
    • Set barcode height / module width (GS h / GS w)
    • Select HRI text position / font (GS H / GS f)
    • Print 2D symbols (GS ( k) β€” QR Code (cn=49), PDF417 (cn=48), DataMatrix (cn=54), Aztec (cn=55)
    • Status / transmit-back: paper & drawer status (GS r), printer ID (GS I), Automatic Status Back (GS a)
    • Download bit image: define (GS *) and print (GS /)
    • Set motion units (GS P), absolute/relative vertical position (GS $ / GS \)
    • Config (accepted/ignored): user setup (GS ( E), print control (GS ( K), response request (GS ( H)

The emulator is bidirectional: status commands (DLE EOT, GS r, GS I) and Automatic Status Back reply to the host over the same TCP/serial connection, driven by the Printer state panel (right side) where you can simulate paper-out/near-end, cover open, cash-drawer open/closed, offline, and error conditions. Like a real device, the emulator refuses to print while it isn't ready (out of paper, cover open, offline, or in an error state) and shows a notification instead.

Barcodes and QR codes render inline on the receipt, with optional HRI text:

Barcode and QR example

Not yet implemented

A few things remain partial or unimplemented:

  • Page-mode coordinate system β€” page mode buffers output and rasterizes it on FF, but absolute/relative positioning (ESC $, GS $, GS \) and print direction (ESC T) are accepted as no-ops rather than fully positioned.
  • User-defined glyph substitution β€” ESC & glyphs are parsed and stored, but the inline text renderer still draws the font glyph rather than the custom bitmap.
  • MaxiCode / GS1 DataBar / Composite 2D symbologies (GS ( k cn=50/51/52).
  • Graphics commands GS ( L / GS 8 L (NV/raster graphics store-and-print) and other ESC *-family densities.
  • Real-time DLE DC4 functions other than the cash-drawer pulse (power-off, recover-and-cancel, buzzer).
  • Katakana / CJK code pages render as missing glyphs since the bundled Latin font has no such glyphs.

Contributions welcome β€” new commands follow the simple BaseCommand pattern in EscPos/Commands and are registered in EscPosInterpreter.RegisterCommands.

Connecting

The emulator accepts ESC/POS data over two transports. Both can be changed live from the UI (left panel): pick a TCP listen address and port and Start/Stop the listener, or select a serial port + baud and Open/Close it (⟳ refreshes the port list). The environment variables below set the initial values at startup:

Variable Default Meaning
ESCPOS_LISTEN_ADDRESS 0.0.0.0 Initial TCP bind address (0.0.0.0 = all interfaces, 127.0.0.1 = localhost).
ESCPOS_TCP_PORT 9100 Initial TCP listen port. Set to off / 0 to start with TCP stopped.
ESCPOS_SERIAL_PORT (unset) Serial device to auto-open (e.g. /dev/ttys004, COM3). Unset = serial closed.
ESCPOS_SERIAL_BAUD 9600 Serial baud rate.
ESCPOS_DEBUG_DUMP (off) Set to 1 to dump every received payload to last_* files.
ESCPOS_RENDER_BACKEND skia Render backend: skia (default) or imagesharp (managed). The --backend arg overrides it.

Examples:

# (run is shorthand for: dotnet run --project src/CrossEscPos.App.Desktop)
dotnet run --project src/CrossEscPos.App.Desktop                  # TCP only, port 9100
ESCPOS_TCP_PORT=9200 dotnet run --project src/CrossEscPos.App.Desktop   # TCP on 9200
ESCPOS_SERIAL_PORT=/dev/ttys004 dotnet run --project src/CrossEscPos.App.Desktop  # TCP 9100 + serial
ESCPOS_TCP_PORT=off ESCPOS_SERIAL_PORT=COM3 dotnet run --project src/CrossEscPos.App.Desktop  # serial only

Pick the render backend (for A/B testing the two rendering libraries) with --backend skia (default) or --backend imagesharp (the managed, no-native backend). The active backend is shown in the window title bar.

dotnet run --project src/CrossEscPos.App.Desktop -- --backend imagesharp   # managed ImageSharp backend
dotnet run --project src/CrossEscPos.App.Desktop -- --backend skia         # default SkiaSharp backend

The status panel shows the active TCP endpoint and serial port.

Testing serial without hardware (app-to-app on one machine)

You don't need a USB serial adapter. Create a virtual serial bridge β€” a pair of linked ports β€” then point the emulator at one end and your POS application (or a shell) at the other. Bytes written to one end appear on the other.

macOS / Linux β€” using socat (brew install socat / apt install socat). A helper script is included:

./scripts/serial-bridge.sh
# It prints a linked pair, e.g.:
#   PORT A (emulator): /dev/ttys004
#   PORT B (your app): /dev/ttys005
# Leave it running.

Then, in two more terminals:

# Terminal 2 β€” run the emulator on port A
ESCPOS_SERIAL_PORT=/dev/ttys004 dotnet run --project src/CrossEscPos.App.Desktop

# Terminal 3 β€” send a receipt from "another app" on port B
cat test_receipt.txt > /dev/ttys005
#   …or from your own program, just open /dev/ttys005 like a normal serial port
#   (9600 8N1) and write ESC/POS bytes to it.

The receipt appears in the emulator window. Because the interpreter is stateful across reads, fragmented serial writes (commands split across packets) are handled correctly.

Windows β€” install com0com and create a linked pair (e.g. COM3 ↔ COM4). Run the emulator with ESCPOS_SERIAL_PORT=COM3 and have your application write to COM4.

Monitor (built-in test client)

Sending test jobs is the monitor's job β€” the emulator is the device, the monitor is the POS-side client that drives it over the wire (just like a real application would). The Monitor is shared by both heads: click Open monitor… to launch it β€” a window on desktop, an in-page overlay in the browser. Its test jobs and status display are identical; only the transport differs:

  • Desktop (built on ESC-POS-.NET) β€” pick a transport:
    • TCP/IP β€” connect to the emulator's listener (or any networked printer).
    • Serial β€” pick a port + baud; pairs with the emulator's serial transport via a virtual port bridge.
    • USB β€” print directly to a real USB printer selected from the connected-device list (by VID:PID), via libusb. This is send-only (no status), and needs native libusb installed (macOS brew install libusb, Debian/Ubuntu apt install libusb-1.0-0; bundled on Windows) and the OS not already holding the device.
  • Browser β€” sends over the SignalR proxy (the CrossEscPos.Host hub) to the in-page emulator, for a full round-trip without any native transport.

It then lets you exercise the target without writing any code:

  • Print a sample receipt, all 1D barcodes, or QR / PDF417 / DataMatrix / Aztec.
  • Send the full feature test receipt, open the cash drawer, buzz, or cut.
  • Watch the printer status the emulator reports back: toggle paper-out / cover / drawer / offline in the Printer state panel and the monitor's status display updates live (via Automatic Status Back), confirming the emulator's status responses are wire-correct. When the printer isn't ready, the emulator drops the job and shows a notification, just like real hardware.

Monitor

Toggling the emulator's Printer state panel pushes status to the monitor in real time β€” here the printer reports paper low and a recoverable error, so the monitor shows Not ready:

Monitor reflecting printer state

Exporting tickets

Each cut (ESC i / ESC m / GS V) starts a new receipt β€” a "page". The Export buttons in the left panel save the rendered tickets as PNG:

  • Export all (single image) β€” stacks every receipt into one tall PNG (a save dialog).
  • Export each cut (folder) β€” writes one receipt_NNN.png per cut into a chosen folder.

Architecture & packages

The emulator is split into layered, independently-publishable packages so the rendering backend is swappable and the core runs headless (and in the browser). The namespace is unified under CrossEscPos.* (organized by feature/directory, not by package), so types resolve across assemblies.

Package Namespace(s) Role Depends on
CrossEscPos.Abstractions CrossEscPos, CrossEscPos.Graphics Backend-agnostic rendering + printer contracts (IReceiptCanvas, IReceiptImage, IReceiptImageFactory, ITypefaceProvider, IImageEncoder, IReceiptPrintable, IPrinterResponder) β€”
CrossEscPos.Core CrossEscPos.Emulator, CrossEscPos.EscPos, … Headless ESC/POS interpreter, printer state machine, receipt document model, barcode/QR generation. ESC/POS code maps (2D families, code tables, barcode systems, status requests) are behaviour-carrying SmartEnums, not magic-number switches Abstractions, QRCoder, ZXing.Net, Ardalis.SmartEnum
CrossEscPos.Rendering.Skia CrossEscPos.Rendering.Skia The default render backend (SkiaSharp). Swap it for another IReceiptImageFactory/ITypefaceProvider/IImageEncoder Abstractions, SkiaSharp
CrossEscPos.Rendering.ImageSharp CrossEscPos.Rendering.ImageSharp A 100% managed render backend (ImageSharp) β€” no native dependency, so it runs in Blazor WASM without a native relink. Same output as the Skia backend Abstractions, SixLabors.ImageSharp.Drawing
CrossEscPos.Transports CrossEscPos.Transports TCP / serial / USB transports (desktop only) Core, System.IO.Ports, LibUsbDotNet, ESC-POS-.NET
CrossEscPos.Controls CrossEscPos.Controls Reusable Avalonia controls (ReceiptView, PrinterStatePanel) β€” host apps consume these. Backend-agnostic (no SkiaSharp dependency) Core, Avalonia

Core carries no UI and no graphics-backend dependency, so the library works headless or in WASM. The host (desktop, browser, or your own app) is the composition root: it picks a backend and injects it.

var imageFactory = new SkiaImageFactory();
var typefaces    = new SkiaTypefaceProvider();
var printer      = new ReceiptPrinter(PaperConfiguration.Default, imageFactory, typefaces);
printer.FeedEscPos(escPosBytes);                         // byte[] β€” ESC/POS is binary
using var image  = printer.CurrentReceipt.Render();      // IReceiptImage
new SkiaImageEncoder().EncodePng(image, outputStream);

πŸ“¦ Package usage guides: docs/packages/ β€” getting started, the core emulator, rendering & custom backends (Skia + managed ImageSharp), the Avalonia controls, and the transports. The desktop and browser apps are one shared Avalonia app (src/CrossEscPos.App).

πŸ“– Full reference & guides: the project wiki, including a step-by-step Adding a render backend guide.

Building & running

Requires the .NET 10 SDK (and the wasm-tools workload for the browser head: dotnet workload install wasm-tools).

# Desktop app (Windows / macOS / Linux)
dotnet run --project src/CrossEscPos.App.Desktop

# Headless: ESC/POS bytes -> PNG, no UI, no Avalonia
dotnet run --project samples/CrossEscPos.Headless -- test_receipt.txt out.png

# Browser: the SAME app in the browser (Avalonia WASM head) β€” full parity with desktop
dotnet run --project src/CrossEscPos.App.Browser

# Web host: serves the browser app AND the SignalR broker on one origin, giving it TCP reception
# (browsers can't open raw sockets). First run publishes the WASM app into wwwroot (cached after).
# Browse to the printed URL; the emulator opens the TCP port itself.
dotnet run --project samples/CrossEscPos.Host

# Tests (unit + headless UI)
dotnet test CrossEscPos.slnx

One app, two heads. The desktop and browser apps are the same Avalonia application (src/CrossEscPos.App) β€” the same views, view models, receipts, printer-state panel, PNG export, and the Monitor test-client. Only the platform edges differ, injected via IPlatformServices:

Desktop head Browser head
Transports TCP + serial Web Serial + WebUSB + SignalR TCP proxy
Export native save dialog browser download (same Avalonia storage API)
Monitor test-client window in-page overlay (SignalR round-trip)

A browser can't open a raw TCP listen socket, so the browser head connects over SignalR to samples/CrossEscPos.Host β€” one ASP.NET Core host that serves the WASM app and runs the broker. The emulator asks the host to open a TCP listener on the address:port you choose; POS software connects there and its jobs bridge to the in-page emulator, with status flowing back. The same hub carries the browser Monitor's jobs, so it round-trips against the in-page emulator too.

  • Windows / macOS: no extra setup β€” native rendering libraries ship with the Avalonia packages.
  • Linux: install the usual font/render native deps if they are missing, e.g. sudo apt install libfontconfig1 libfreetype6 (Debian/Ubuntu).

Testing

Two test projects under tests/:

  • CrossEscPos.Core.Tests β€” emulation coverage. ESC/POS sequences are fed through the interpreter and the observable results (rendered draws, printer state, host responses, events) asserted: text and control characters, styling (emphasis/italic/size/justification), cutting and feeding, 1D/2D barcodes and bit images, status/transmit-back (DLE EOT, GS r, GS I, GS a), cash drawer and buzzer, real-time commands, code pages, page mode and not-ready handling β€” plus exact StatusByteBuilder bit layouts and the SmartEnum code maps. Rendering goes to a synthetic backend, so the suite runs with no SkiaSharp (proving the core is headless).
  • CrossEscPos.Controls.Tests β€” headless Avalonia UI tests for the controls (two-way state binding, receipt image rendering).
dotnet test CrossEscPos.slnx

Fonts & license

Receipt text is rendered with JetBrains Mono, embedded in the CrossEscPos.Rendering.Skia package (under src/CrossEscPos.Rendering.Skia/Assets/Fonts/) so output is identical across platforms and works in the browser sandbox (no file IO). JetBrains Mono is licensed under the SIL Open Font License 1.1; the full license text is included at src/CrossEscPos.Rendering.Skia/Assets/Fonts/OFL.txt. Per the OFL, the font is redistributed here under its original license and "JetBrains Mono" is a trademark of JetBrains s.r.o. To swap in a different monospace font, replace the receipt-mono*.ttf files (and keep its license alongside).

Emulated printer

This program emulates a printer with the following specifications:

  • 80mm paper width
  • 72mm printing width
  • 180x180dpi
  • ASCII Font A/B: 12x24 pixels
  • Automatic line feed

About

πŸ–¨οΈ Receipt printer emulator to test ESC/POS commands against

Resources

License

Stars

0 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages

  • C# 93.6%
  • JavaScript 3.6%
  • Shell 1.3%
  • Python 1.3%
  • Other 0.2%