The POS895 does not answer Epson's ENPC broadcast, so PrintServer never
registers it and every job for it fails with "Printer not found". The
feasibility notes listed the vendor's own discovery protocol as unknown
and expected a packet capture to recover it, which is what this does.
Captured pktmon traffic while running the vendor EasySet tool on the
printer's segment. The protocol is:
query UDP broadcast 255.255.255.255:3289, sent FROM source port 9100
53 50 52 54 2D 49 6E 66 6F 72 00 00 0D "SPRT-Infor" 00 00 0D
reply from <printer>:3289 back to <querier>:9100, 39 ASCII bytes
"28-0e-8b-3d-ca-f1 2608120478"
MAC, 17 chars 12 spaces serial, 10 chars
The printer's address is the reply's source IP. Pos895Test now sends this
probe and parses the replies; run against the real printer it returns the
same MAC and serial the vendor tool got, so the reimplementation matches.
Two useful side effects. Port 3289 is the same UDP port ENPC uses, so a
composite IDiscoveryService can share one socket. And the querying machine
and the printer sat in different /24s of one flat segment, so the /24 TCP
sweep the notes originally preferred would have missed this printer.
The print test is commented out at the bottom of Program.cs rather than
deleted; it confirmed Font A is 48 columns and that GS ! magnification
works at width 1x/2x/3x with height 2x.
Notes updated: the discovery work item is no longer the largest one, the
compatibility table records DLE EOT and GS ! as confirmed and ESC d as
apparently ignored, and the answered questions are separated from the
ones still open.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
13 KiB
POS895 / POS896 printer support: feasibility notes
Date: 2026-09-02, updated 2026-09-03 after a hardware session. Status: several
of the open questions below are now answered, and the vendor discovery protocol
is reverse engineered and reimplemented. See Pos895Test/.
Source material reviewed: D:\Inspectron\James\epson\POS895\POS895&896 SDK\ (Linux SDK, Windows POS_SDK, EasySet tool, POS895 and POS896 user manuals, POS_SDK Development Manual V1.4).
Verdict
The POS895 and POS896 can be supported by this codebase, but not by linking the vendor SDK. The SDK is a thin C wrapper that builds ESC/POS byte strings and writes them to a raw TCP socket on port 9100. Inspectron.Epson/EpsonPrinter.cs already does the same thing in C#. The real work is:
- a new
IPrinteradapter for a 576-dot, 80 mm ESC/POS clone, - a discovery strategy that does not rely on Epson's ENPC broadcast,
- a more robust printer identification than the single
GS I 1byte, - a hardware probe session to close the unknowns listed below.
Rough effort: 3 to 5 developer days once a printer is on the bench. No new deployment dependencies, no native code, no libusb. Restrict to the Ethernet models (POS895 UEd, POS896 UE); USB-attached operation is a separate project because the whole pipeline keys on an IP address.
What the vendor SDK is
Path: Linux_SDK\Linux_SDK_20190110\Linux_SDK_64_group_ARM\Linux_SDK_64_group\
| File | What it is |
|---|---|
HWISPRTPrinter.c / .h |
~5,500 lines of C. Transports: libusb, serial tty, TCP socket to port 9100. Every POS_* function fills a global 1 KB buffer with an ESC/POS sequence and writes it. |
sprtGraphic.c / .h |
BMP loader and a proprietary line-bitmap encoder (see below). |
libHWISPRTPrinter.so |
Prebuilt library. 32-bit Intel i386, despite the folder name saying ARM. |
libusb/ |
Bundled libusb, x86-64. |
testMain.c, CPCLtest.c, TSPLtest.c, CPCLDemo.c, GtestMain.c |
Demos. Only testMain.c is ESC/POS. |
makefile |
Cross-compiles with arm-linux-gcc. The shipped binaries were not built with it. |
README.txt |
Chinese, GBK encoded. Decode with iconv -f GB18030 -t UTF-8. The .c and .h files are UTF-8. |
Reasons not to use it:
- Global state only. One socket descriptor (
socketFd), one command buffer (gCmdBuf), no printer handle. It cannot drive two printers concurrently;PrintServerdoes. - The shipped
.sowould not load on the Raspberry Pi. It would need cross-compiling from source, then P/Invoke from .NET, for zero functional gain over whatEpsonPrinteralready sends. - Roughly half the API is CPCL and TSPL label-printer commands. Irrelevant for receipts.
pos-sdk\POS-SDK\POS-SDK_V1219\is a WindowsPOS_SDK.dllwith a WinForms P/Invoke demo (Testing\C#\TestSDKCSharp\Form1.cs). Irrelevant for the Linux service. Its manual (POS_SDK Development Manual V1.4_Neutral.pdf) is in English and is the best command reference in the package.- Development stopped around 2016 to 2019 for the Linux part.
The firmware family is "SPRT" (the SDK is named HWISPRTPrinter, log prefix qzf). Treat the printer as a SPRT-derived ESC/POS clone.
Printer facts from the manuals
| POS895 | POS896 | |
|---|---|---|
| Paper | 80 mm (79.5 ±0.5), 72 mm printable | same |
| Resolution | 203 dpi, 8 dots/mm, 576 dots/line | same |
| Fonts | Font A 12x24 (48 columns), Font B 9x17, CJK 24x24 | same |
| Interfaces | Ud: USB. UEd: USB + Ethernet | UBTDM/UBTMM: USB + Bluetooth + drawer. UE: USB + serial + Ethernet + drawer |
| Command set | ESC/POS | ESC/POS |
| Barcodes | UPC/EAN/CODE39/ITF/CODABAR/CODE93/CODE128, PDF417, QR, DataMatrix | same |
| Speed | 160 mm/s | 260 mm/s |
| DHCP | Off by default, auto-enables if the static IP cannot reach the router | same |
| Factory reset | 1B 09 1B 92 FF 01 |
|
| Hex dump mode | Open cover, press FEED twice within 3 s, close cover. Exit: open cover, press FEED three times. | |
| Self-test | Press FEED twice within 3 s. Prints model, firmware, interface, default code page. |
The POS896 buffers up to 45 KB of unprinted data across a paper-out and prints it after reloading. Worth knowing when reasoning about retries.
ESC/POS commands the SDK emits (so the firmware certainly supports them)
| Function | Bytes |
|---|---|
| Initialize | 1B 40 |
| Feed n lines | 1B 64 n |
| Cut, full / partial | 1D 56 00 / 1D 56 01 (SDK always feeds 3 lines first) |
| Line spacing | 1B 33 n |
| Alignment | 1B 61 n |
| Left margin | 1D 4C nL nH |
| Font mode (font B, bold, double width, double height, underline) | 1B 21 n (bits 0, 3, 5, 4, 7) |
| Bit image | 1B 2A m nL nH data, m in {0, 1, 32, 33} |
| Real-time status | 10 04 n, n in 1..4, one-byte reply |
| Printer ID | 1D 49 42 (GS I 66, returns "_" + factory name as a string) |
| Cash drawer | 1B 70 m t1 t2 |
| Inverse print | 1D 42 n |
| Rotate / upside-down | 1B 56 n / 1B 7B n |
| 1D barcode | 1D 6B ... |
| 2D barcode | 1D 5A ... (PDF417 / DataMatrix / QR selector) |
| Proprietary line bitmap | 1D 44 01, then per non-blank row 15 <skip> 16 <bytes> <inverted row data>, then 1D 44 00 |
Status byte interpretation in the SDK (matches Epson's DLE EOT layout and our parsers in EpsonPrinterStatus.cs):
| Query | Fixed bits | Meaning of set bit |
|---|---|---|
10 04 01 printer status |
0x12 must be set |
0x04 drawer high |
10 04 02 offline status |
0x12 |
0x04 cover open |
10 04 03 error status |
0x12 |
0x08 cutter error, 0x20 unrecoverable, 0x40 auto-recoverable |
10 04 04 paper status |
0x12 |
0x40 paper end |
Code pages: the SDK manual's code page table (TSPL section, but the same family) has index 18 = CP852, matching Epson numbering. Our EpsonCommands.CP852Encoding sends 1B 74 12, so Latin-2 text is probably fine. Confirm on the self-test page.
Compatibility of what TM_T30IIITranslated sends today
| Command | Our use | Assessment |
|---|---|---|
| ESC @, GS V, ESC 3, ESC 2 | init, cut, line spacing | Fine, SDK emits them. |
| ESC d n | feed | Appears to be ignored. Feed with literal LF bytes. |
| DLE EOT 1..4 | status | Confirmed. Identical layout, 0x12 fixed bits set. |
GS ! n (SetFontSizeAsync) |
magnification | Confirmed at width 1x/2x/3x, height 2x. |
ESC E (SetEmphasized) |
bold | Standard, likely fine. SDK uses ESC ! bit 3 instead. |
| ESC t 18 + CP852 | umlauts | Likely fine, see code pages above. |
ESC $ (SetAbsolutePrintPosition) |
logo offset 100 | Unverified. SDK uses GS L margins instead. |
GS ( L fn 112 / fn 50 (LoadImageAsync + PrintLoadedImage) |
logo | Probably unsupported. SDK never uses it. Fallbacks: ESC * 24-dot double density (PrintImageBitModeAsync, already implemented) or GS v 0 (not implemented). |
GS I 1 (GetPrinterIdAsync) |
model byte | Unknown. SDK uses GS I 66 for a maker string instead. |
ESC r (SetRedColor) |
two-colour | Not applicable, monochrome printer. The T30III adapter ignores IsRed anyway. |
SPRT discovery protocol
Captured with pktmon while running EasySet Tool.exe on the printer's segment,
then reimplemented in Pos895Test and confirmed to return byte-identical results
(same MAC, same serial) as the vendor tool.
Query - UDP broadcast to 255.255.255.255:3289, sent from source port 9100, 13 bytes:
53 50 52 54 2D 49 6E 66 6F 72 00 00 0D "SPRT-Infor" + 00 00 0D
Reply - from <printer>:3289 back to <querier>:9100, 39 bytes of ASCII:
28-0e-8b-3d-ca-f1 2608120478
|<--- MAC, 17 --->||<- 12 sp ->||<- serial, 10 ->|
The printer's address is the reply's source IP. The payload carries MAC and serial only, no model name, so model identification remains a separate TCP-side step and cannot be folded into discovery.
- Port 3289 is the same UDP port Epson ENPC uses, so a composite
IDiscoveryServicecan reuse the socket it already has and dispatch on the shape of the reply. - The querying machine was
10.1.250.71and the printer10.1.252.219: different /24s on one flat segment. A /24 sweep would have missed this printer, which is why the capture route beat the port-sweep route here. - Unverified: whether the firmware replies to the sender's source port or
hard-codes 9100.
Pos895Testbinds 9100 to match EasySet. - Unverified: reply framing when more than one printer answers.
What breaks today if a POS895 is plugged into the network
- Discovery.
PrintServerBootstrapperbindsDiscoveryService, which broadcasts Epson's proprietaryEPSONQpacket on UDP 3289. The POS895 never answers it (confirmed: anEPSONQbroadcast went unanswered in the same capture that the printer did answer SPRT discovery in).PrintServer.RegisterPrinteris never called, soSubmitJoblogs "Printer not found" and reports the job Failed.JamesDiscoveredPrintersReceivernever tells the backend about the printer, so it cannot be assigned to a work area. - Identification.
EpsonPrinter.GetPrinterIdAsyncsends1D 49 01and reads one byte. Three places dispatch on that byte:PrinterWrapperFactory.CreatePrinterFromId,ReceiptConverterFactory.Create(48 vs 33 columns), andDiscoveryService.GetModelNameFromId. If the printer answers nothing, the read waits the full connection timeout (10 s), returns null,printerId.Valuethrows, the job fails withPrintErrorType.Other, andPrinterQueueretries three times. If it answers0x01it is silently treated as a TM-T30III and mostly works by accident, with a 384 px logo at offset 100 on a 576-dot line. - Paper-out logic.
EpsonPrintService.PrintAsyncmaps!PrinterStatus.IsOnline(DLE EOT 1 bit 3) toOutOfPaper. Whether this firmware sets that bit on paper-out is unverified. DLE EOT 4 bit 6 is what the SDK uses.
Work items
- Hardware probe (do first, half a day). With the printer in hex dump mode and then in normal mode, send and record replies for:
1D 49 01,1D 49 41,1D 49 42,1D 49 43(what identifies the model, and how many bytes come back)10 04 01..04in normal, paper-out, cover-open states1B 74 12followed byä ö ü ß čbytes in CP8521D 21 00 / 11 / 10 / 01text sizes1B 24 nL nHthen text (does absolute positioning work)- a logo via GS ( L, via ESC * 33, and via
1D 76 30 EpsonDiagnostics.RunFullDiagnosticsAsyncoutput for the record
- Adapter. New
Inspectron.Epson/PrintServer/Printers/POS895.csimplementingIPrinter, cloned fromTM_T30IIITranslated: logo width 576, centred offset(576 - width) / 2, image via ESC * or GS v 0, delays tuned. One case each inPrinterWrapperFactory,ReceiptConverterFactory(48 columns),DiscoveryService.GetModelNameFromId. - Identification hardening in
EpsonPrinter. Shorter read timeout for GS I, fall back to the GS I 67 model-name string, or to a per-IP configured model, when the byte is missing or collides with an Epson ID. - Discovery for non-Epson printers. Solved: the vendor SPRT protocol is documented above and reimplemented in
Pos895Test, confirmed against the printer. Remaining work is aSprtDiscoveryServiceimplementingIDiscoveryService, and a composite service that merges its results with ENPC. Both probes go to UDP 3289, so they can share a socket.PrinterDiscoveryBackgroundTaskneeds no change. Now well under the 1 to 2 days originally estimated. - Emulation and tests. Add a 576 px
HtmlPrinterentry with the new ID to the emulation dictionary inPrintServerBootstrapper, and EpsonTest cases, so receipt layouts can be checked without hardware. - GS v 0 raster in
EpsonCommands, only if the probe shows GS ( L is rejected. - CLAUDE.md updates once the adapter exists: printer ID byte, image width, discovery method.
Answered on hardware, 2026-09-03
Probed through an AnyDesk TCP tunnel to the printer's port 9100, plus the SPRT
discovery probe run on the printer's own segment. Tooling in Pos895Test/.
- Does the printer answer any UDP discovery? Yes, SPRT on UDP 3289, above.
- Does DLE EOT work? Yes. All four queries answered, exact Epson bit layout,
0x12fixed bits set on every reply.EpsonPrinterStatus.csparses this printer unmodified, so no custom status parser is needed. - Font A column count? 48 on the 576-dot line, as the manuals claim. The existing 48-column converters apply unchanged.
- Does
GS !magnification work? Yes, verified at width 1x/2x/3x, height 2x, soSetFontSizeAsynccan pass magnification through without clamping. ESC !? Selects Font A with0x00.ESC d n? Appears to be ignored. Feed with literal LF bytes.
Open questions that only hardware can answer
- What does
GS I 1return, if anything? - Does the firmware accept
GS ( Lraster graphics? - Is code page 18 really CP852 in ESC/POS mode?
- Does DLE EOT 1 bit 3 go high on paper-out? The status queries answer, but paper-out was never induced.
- Font B, and its claimed 64 columns.
- Does the SPRT reply go to the sender's port, or is 9100 hard-coded?