ESC/POS Printer Status: DLE EOT Explained

The DLE EOT real-time status commands byte by byte — paper, cover, drawer, errors — plus why many printers lie or stay silent, and what to do about it.

Published 2026-07-24 · For developers

DLE EOT is ESC/POS's real-time status query: the host sends the three bytes 10 04 n (n = 1 to 4) and the printer answers with a single status byte carrying paper, cover, drawer, and error flags. It is the only part of a normal ESC/POS job that is a conversation rather than a one-way byte stream — and the only in-band way to know a raw-socket or USB printer is actually able to print. This page is the byte-level reference, plus the ways real printers get it wrong.

The request: 10 04 n

DLE is byte 0x10, EOT is 0x04, and n selects which of four status bytes you want:

QueryBytesAsks for
DLE EOT 110 04 01Printer status — drawer pin, online/offline
DLE EOT 210 04 02Offline-cause status — cover, feed button, paper end, error
DLE EOT 310 04 03Error-cause status — cutter, unrecoverable errors
DLE EOT 410 04 04Continuous-paper sensor — near-end and end

Two properties make DLE EOT special. First, it's real-time: Epson-class firmware parses it as bytes arrive and answers immediately, even while a large job is still buffered ahead of it — that's the entire point, since a busy printer is exactly when you want status. Second, the reply is a single naked byte — no framing, no echo of n — so if you send multiple queries you must track which answer belongs to which question yourself.

Reading a reply byte

Every reply, for every n, shares one structural skeleton: bits 1 and 4 are always 1, bits 0 and 7 are always 0. So the baseline "all clear" reply is:

0 0 0 1 0 0 1 0  =  0x12
7 6 5 4 3 2 1 0

That skeleton is your validity check. A byte with bit 0 or bit 7 set is not a DLE EOT reply — it's job data, line noise, or a different message class:

function isStatusReply(byte) {
  return (byte & 0x12) === 0x12 && (byte & 0x81) === 0x00;
}

This matters because the reply arrives on the same channel as everything else. (It's also how you tell DLE EOT replies apart from ASB packets — see below — whose first byte has bit 1 clear.)

The four replies, bit by bit

Flag bits below follow the Epson TM convention, which the clone market copies with varying fidelity. Bits not listed are the fixed structural bits or unused.

DLE EOT 1 — printer status

BitMaskMeaning when set
20x04Drawer kick-out connector pin 3 is high
30x08Printer is offline

The drawer bit reports a voltage, not a truth: whether pin 3 high means "drawer open" or "drawer closed" depends on how the drawer's microswitch is wired. Calibrate per install — open the drawer, query, and see which way your hardware reads.

DLE EOT 2 — offline-cause status

BitMaskMeaning when set
20x04Cover is open
30x08Paper is being fed by the feed button
50x20Printing stopped: paper end
60x40An error condition exists

Bit 6 is an aggregate: cover open, paper out, or an offline state each raise it. Many POS health checks read only this byte and branch on 0x40.

DLE EOT 3 — error-cause status

BitMaskMeaning when set
30x08Autocutter error
50x20Unrecoverable error
60x40Auto-recoverable error (per Epson's TM reference — e.g. head over-temperature; clears itself)

DLE EOT 4 — continuous-paper sensor

BitMaskMeaning when set
2 + 30x0CPaper near-end (roll is low)
5 + 60x60Paper end (out)

The doubled bits are not sloppy documentation — the spec genuinely mirrors each paper sensor across two adjacent bits, and printers in the wild set both. Mask with 0x60 and 0x0C, and note that paper-end replies set the near-end bits too: check end first, and report "near end" only when the end bits are clear.

Worked examples: the golden table

Cross every meaningful printer state with every query and you get a compact truth table. This one is executable — it's the table the ProxyNodes simulator, firmware (117 C tests under AddressSanitizer), and conformance suite are all verified against, byte for byte:

Staten=1n=2n=3n=4
All clear0x120x120x120x12
Paper low0x120x120x120x1E
Paper out0x120x720x120x7E
Cover open0x120x560x120x12
Drawer pin high0x160x120x120x12
Offline0x1A0x520x120x12

Worth decoding one by hand: paper out on n=2 is 0x12 (base) + 0x20 (paper end) + 0x40 (error present) = 0x72; on n=4 it's 0x12 + 0x0C (near-end bits, mirrored) + 0x60 (end bits) = 0x7E.

Querying over a raw socket

DLE EOT rides the same TCP connection as port 9100 raw printing, which is what makes it useful: one socket both prints and health-checks.

import net from "node:net";
 
function queryStatus(host, n, timeoutMs = 500) {
  return new Promise((resolve, reject) => {
    const sock = net.createConnection(9100, host);
    const timer = setTimeout(() => {
      sock.destroy();
      resolve(null); // no answer — status UNKNOWN, not "ok"
    }, timeoutMs);
    sock.once("data", (buf) => {
      clearTimeout(timer);
      sock.end();
      resolve(buf[0]);
    });
    sock.once("error", reject);
    sock.write(Buffer.from([0x10, 0x04, n]));
  });
}
 
const paper = await queryStatus("192.168.1.87", 4);
if (paper === null) console.log("printer did not answer");
else if ((paper & 0x60) !== 0) console.log("PAPER OUT");
else if ((paper & 0x0c) !== 0) console.log("paper near end");
else console.log("paper ok");

One parser subtlety if you're on the receiving side (writing a print server, proxy, or emulator): 0x10 is a perfectly ordinary byte inside raster image data. A stream parser must only treat it as DLE when it's actually followed by 04 and a valid n — and must handle a query split across two TCP segments, because real POS software splits mid-sequence constantly. Consuming every lone 0x10 as a query corrupts graphics; real printers make the same narrow match.

ASB: the asynchronous alternative

Polling has a cousin: Automatic Status Back, enabled with GS a n (1D 61 n). Instead of being asked, the printer pushes a 4-byte status packet whenever a monitored condition changes — paper runs out, cover opens, drawer closes. The bits of n select the triggers (drawer pin, online/offline, error, and paper-sensor changes; 1D 61 FF enables everything, 1D 61 00 disables ASB).

ASB is how you get event-driven status — no polling loop, no missed transitions between polls. The costs: the four ASB bytes arrive unsolicited and interleave with any DLE EOT replies on the same channel, so your reader needs to classify bytes (the structural bits above are how); and ASB support is less uniform across the clone market than DLE EOT, so verify it on your actual model before depending on it. Epson's TM manuals define the exact 4-byte layout per model.

Why many printers answer nothing — or lie

Here's the part vendor documentation won't tell you. Across the real ESC/POS market:

  • Some printers never answer. Budget clones implement the printing commands and skip the status ones. Your query is swallowed; your read times out.
  • Some answer, but lie. A printer without a near-end sensor (an optional part on many models) reports paper-fine until the roll is literally empty. Some clones hardwire 0x12 — permanently "all clear" — because it makes them pass naive integration tests.
  • Some USB paths are write-only. DLE EOT needs a return channel. A number of cheap USB printers (and USB-serial bridge chips) expose only an OUT endpoint or never service the IN side, so status is physically impossible over that cable no matter what the firmware knows. This bites WebUSB printing especially hard.
  • "Real-time" isn't always. Genuine Epson firmware answers DLE EOT ahead of buffered job data; some clones parse it in-line, so the reply arrives after the queued job — seconds or minutes late, describing a state that no longer exists.
  • Bit meanings drift. ESC/POS is a standard every manufacturer forks. n=3 layouts and drawer-pin polarity are the usual drift points; the golden table above is the Epson TM behavior that clones aim for and don't always hit.

The operational consequence: a status system that defaults to "OK" is worse than no status system. If your code treats a timeout as success, every write-only printer in your fleet reports healthy forever — right up until the support ticket.

Designing for honest status

The correct contract for anything that reports printer status — a print server, a proxy device, your own integration layer — has three rules:

  1. Query, don't assume. Run DLE EOT (or ASB) against the live printer; never synthesize "online" from a successful TCP connect or USB enumeration.
  2. Timeouts mean unknown. No reply is a distinct, reportable state — not an error, not an all-clear.
  3. Say so downstream. Expose "I can't tell" to the caller, so UIs can show "status unavailable" instead of a green checkmark that's a guess.

This is how ProxyNodes is built. A ProxyNode — prototype-stage hardware, running on our bench today — answers DLE EOT on its :9100 emulation from live sensor state, per the golden table above — so a legacy POS health-checking "the printer" gets real answers. On the JSON side, GET /status reports known: false whenever the reading genuinely can't be taken (write-only endpoint, stub driver, unresponsive hardware) rather than inventing a healthy printer. And because the digital twin implements the same byte table with fault injection — paper out, cover open, offline as REPL commands — you can test your entire status-handling path, including the lying-printer cases, before any hardware exists. See ESC/POS emulators for how that compares with other simulation options, and the ESC/POS command reference for the rest of the command set.

Frequently asked questions

What does DLE EOT stand for?
Data Link Escape (0x10) and End of Transmission (0x04) — ASCII control characters that ESC/POS repurposed as a two-byte prefix for real-time requests. The full query is 10 04 n, where n from 1 to 4 selects printer, offline-cause, error-cause, or paper-sensor status.
How do I check paper status on an ESC/POS printer?
Send the three bytes 10 04 04 and read one byte back. Bits 5 and 6 (mask 0x60) set means paper out; bits 2 and 3 (mask 0x0C) set with the end bits clear means near-end. No reply within a short timeout means status is unknown — treat it that way, not as OK.
Why does my printer never answer DLE EOT?
Three common causes: the firmware simply doesn't implement status (budget clones), the USB path is write-only so replies can't physically return, or your parser is reading the reply into the wrong place. Test the same code against a known-good printer or a simulator that implements the byte table to isolate which.
What is the difference between DLE EOT and ASB (GS a)?
DLE EOT is polling: you ask, the printer answers one byte. ASB (GS a n) is push: the printer sends a 4-byte packet automatically whenever a monitored state changes. ASB avoids polling gaps but is less consistently implemented on clone hardware, and its packets interleave with other replies on the same channel.
Can I trust a 0x12 'all clear' reply?
Mostly, but not blindly. Printers without a near-end sensor report paper fine until it's empty, and some clones return 0x12 unconditionally. If status matters to your business logic, verify each printer model's replies against induced faults — pull the paper, open the cover — before trusting it in production.
Is DLE EOT enough to confirm a receipt actually printed?
It's the best in-band tool raw ESC/POS offers: query before the job to avoid printing into a dead printer, and after to catch paper-out mid-job. But it reports sensor state, not per-job completion — there is no job protocol in raw printing, so pair status checks with careful job sequencing.

Related reading