Stripy Horse
Stripy Horse

What the virtual printer does

A Stripy Horse printer is not a mock that swallows bytes and answers 200. It speaks the wire protocol a Zebra speaks, on the port a Zebra listens on, and answers status queries byte for byte the way the GX430t on the desk here answers them: the responses are pinned to captures from that machine. This page is the whole feature list, generated from the code that implements it, gaps and all. Try the public one or make your own.

Talking to it

Every printer gets its own address on the raw port real printers use, and an HTTPS endpoint for everywhere a socket cannot reach.

TCP 9100 Raw socket, the port every label printer listens on. Send a stream of ^XA...^XZ formats and each one becomes a job.
Shared port 9100 One address to configure. A stream says which printer it is by carrying that printer's token, or by arriving from an address the account has claimed. A connection that does neither is refused with NO PRINTER: it never falls through to the demo or to anyone else's printer.
The token goes in a comment ^FX STRIPYHORSE:<token> ^FS, anywhere in the first few kilobytes of the stream. It is a ZPL comment, so the label still begins with ^XA for clients that check their own output before sending it, and a real Zebra handed the same bytes prints the label and ignores the line. It is taken out before rendering, so what is stored is the label you wrote.
Who can print to it Three modes per printer. Named by its token is the default and the only one that works from a CI runner whose address is different every run; open answers anyone who knows the host and port, which is what a stock printer driver needs; claimed addresses only answers the machines you have claimed, which is the mode for a system whose output you cannot change: an old WMS or shipping station that emits the label it has always emitted, where the address it arrives from has to name the printer because nothing in the stream can. The last two need a port of the printer's own.
HTTPS ingest POST the same bytes to the printer's ingest URL when outbound 9100 is blocked. The body takes any mix the socket takes.
Webhooks A signed POST per job, carrying the label URLs, for pipelines that would rather be told than poll.
Live feed Server-sent events per printer: the job wall updates as labels arrive, and plays each one back at its print speed.

Host queries

The status conversation, byte for byte as a GX430t answers it. Fixtures captured from real hardware hold every response in place.

~HS Three-line host status: paper out, pause, label length, formats in the buffer, head up, ribbon out, temperature flags.
~HI Model, firmware version, dots per millimetre and memory, as the printer identifies itself.
~HM Memory status: total, maximum user and available RAM, the last shrinking as you download objects.
~HD The head diagnostics block, sensor readings and temperatures included.
~HQ Query codes ES (error status), SN (serial number) and HA (head attributes).
getvar SGD reads over the same socket: `! U1 getvar "device.product_name"` and friends.
setvar SGD writes for the variables real firmware lets you set; the rest are swallowed, as firmware swallows them.

Control commands

The commands that change the printer rather than print a label. None of them answer on the wire, exactly like the real ones.

~PP / ~PS Pause and resume. A paused printer holds incoming jobs in its buffer and prints them when it resumes.
~JA Cancel all: the held queue is dropped.
~JX Cancel the job that would print next.
~JC Calibrate. A virtual head is always calibrated, so this is accepted silently.
~WC Print the configuration label: the settings receipt lands in the inbox as a real, readable label.
~RO Reset the resettable counter, as the front panel does.
~SD Set darkness, 0 to 30. It sticks, and shows up in the status and in print.tone.
~TA Set the tear-off position, -120 to 120 dots. It sticks, in ezpl.tear_off.

Objects and formats

The printer's own filesystem: fonts, graphics and formats downloaded once and recalled by name, on the drives the hardware has.

~DG / ~DY Download graphics and objects (GRF, PNG, TTF, TTE) to R: or E:, then draw them with ^XG.
~DU / ~DT TrueType font downloads, recalled through ^A@ or an alias.
^DF / ^XF Store a format and recall it, filling ^FN fields with ^FD data, the way a template-driven line does.
^CW Alias a downloaded font to a single character so ^A calls reach it.
^HW Directory listing for a drive, wildcards included, answered on the wire.
^WD The same listing printed as a label, drive totals included.
^HH The configuration sent to the host as text: the fields the configuration label carries, without printing one.
^ID Delete objects by name or pattern.
Drives R: RAM and E: onboard flash, sized like the hardware; a download that would overflow one is refused.
ROM catalog The resident fonts and graphics a real printer ships with are listed alongside yours.

Printing

What happens to a format once it arrives.

Rendering Every job is rendered to PNG by the same engine the API uses, at the printer's media size.
Auto-size A printer created without a size takes each job's ^PW and ^LL, falling back to 4x6 when the stream says nothing.
^PQ copies Quantity is honored: copies are captured in order, up to 25 of them per job.
^CD / ~CD The parameter delimiter can be moved, which is what a stream does when its own data is full of commas. Data keeps whatever it contains, including the new delimiter. Graphics and downloads are the exception: their parameters are not re-read, so a stream that moves the delimiter and downloads a bitmap is reported rather than risked.
^SF masked serials The other serialized field: a mask laid over the ^FD data says which positions move and in what alphabet, so ^FD12A^SFnnn,1 counts 12A, 12B, 12C and ^FDBL0000^SF%%dddd,1 leaves the letters alone. Positions carry into the one on their left, as a counter does.
^SN serial numbers A serialized field advances once per copy, leading zeros kept when the field asks for them, so ^PQ5 over ^SN0099,1,Y captures 0099 through 0103 rather than five of the first one. A prefix ahead of the digits is carried along.
^PR print speed Speed comes from the job, else the printer's media.speed, and is clamped to what the printhead can do (6 in/s at 203 dpi, 4 at 300), the way firmware clamps it. The wall plays each label back over the seconds that feed would really take.
^PO / ^PM Inverted and mirrored labels are captured as the head would print them, not as they were written.
Odometer Labels fed are counted, and the count outlives a restart the way a real printer counts across power cycles. ~RO resets it, and that outlives a restart too.
Linear barcodes ^BA Code 93 (both check characters, verified by a scanner decoder in the tests), ^BL LOGMARS, ^BM MSI with all four of its check-digit modes, and ^B1 Code 11 with one check character or two. ^B3's and ^BK's check digits are honored now, where both used to be parsed and dropped.
Retail barcodes ^BU UPC-A, ^B9 UPC-E, ^B8 EAN-8 and ^BS add-ons encode for real, check digit and all, beside the Code 128, Code 39, Codabar, Interleaved 2 of 5, EAN-13, QR, Data Matrix, Aztec, PDF417 and MaxiCode already here.
Retention Each printer keeps its most recent jobs and prunes the rest; ephemeral printers expire on their own.

Breaking it on purpose

Every fault a front panel can show, on a switch. Blocking faults hold jobs in the buffer, which ~HS reports, and clearing one flushes the queue in order.

Paper out Blocks printing and shows in ~HS and ~HQES.
Paused Blocks printing; ~PS or the switch resumes it.
Head up The printhead latch, open. Blocks printing.
Ribbon out Thermal transfer with nothing to transfer. Blocks printing.
Over temperature Blocks printing until it clears.
Under temperature Reported, but the printer keeps going, like the real one.
The roll runs out Fit a roll of so many labels and a ribbon of so many metres and they run down as labels print. Paper out and ribbon out raise themselves when they are spent, and a job that reaches the end of the roll prints what is left of it. Zero is an endless roll, which is what a printer starts with.

Not simulated

What a virtual printer cannot or does not yet do. Listed because a gap you know about is a gap you can work around.

^MU Units stay in dots: a format that switches to millimetres or inches lands in the wrong place. Preflight reports it rather than leaving it silent.
^JM Half-resolution mode is ignored, so the label renders at full size.
8 barcode symbologies CODABLOCK, Code 49, GS1 DataBar, MicroPDF417, POSTNET, Planet Code, Plessey and TLC39 have no encoder here. They are drawn as a marked placeholder and reported, never as silent text.
RFID ^RF and its family do nothing: there is no tag to encode and no read to verify.
~JD and ~JE Hexdump mode is not implemented: the printer always prints rather than echoing what it was sent.

Missing something you need? Tell me. The list above is checked against the printer's own command tables by a test, so it says what the simulator does today, not what it meant to do.