Sending Raw EPL2 Directly to a Zebra LP2844 via C#

Sometimes, our vendors send us products with incorrect barcodes or without UPCs. We have a requirement such that every item in our warehouse must have a barcode for inventory control purposes. So when these barcode-less items arrive, we need to print out UPC labels for them. We have a thermal label printer, and we know that it can print out barcodes, so let’s figure out a way to have our application create these barcode labels automatically.

Neat! We can generate this from C# with a text file.

Neat! We can generate this from C# with a text file.

The printer you’ve probably seen before

This little thermal label printer is seemingly ubiquitous in the retail and warehouse worlds:

The good ol' Zebra LP2844.

The good ol' Zebra LP2844.

Everyone seems to have standardized on this little guy–you can rent one from UPS or FedEx to print shipping labels, PayPal can print to it, and so can Stamps.com and Endicia. They retail for nearly $400, but nobody pays that; you can pick up some battered beauties for about $70 on eBay. And the model hasn’t really changed much in over a decade, with the only major revision being the addition of USB support. There are some slight differences in the UPS and FedEx-supplied models because they install a custom firmware, but for our purposes, they’re all the same printer.

The native language of the printer isn’t PostScript or PCL but instead a proprietary language called EPL2 (most commonly just called EPL, without the “2″). It’s short for Eltron Printer Language. Eltron was the company that originally designed this model of printer, and Zebra bought them up a decade ago. Zebra has their own printer language–ZPL, or Zebra Printer Language–that’s similar to ZPL and is used on the TLP2844Z model, but the languages aren’t compatible with one another. Since there was already a lot of legacy code out there working with EPL, and since EPL itself isn’t broken, Zebra continues to offer printers that support EPL. This is the language that UPS WorldShip and FedEx Ship Manager use to print out UPS shipping labels from these devices.

It turns out that EPL is conceptually quite simple, and we can relatively easily add native support for it in our .NET applications. But first, let’s consider why we don’t just take the GDI route.

Why not print using PrintDocument and GDI+?

Zebra provides an advanced printer driver for the LP2844 that allows Windows to see it as any old GDI-based printer. If you open up a Word document, type some stuff into it, and print it to your LP2844, you’ll indeed get your document printed out as you expect. But there are a few disadvantages to this approach:

  • The GDI conversion is much slower than when sending commands with EPL.
  • The printer driver has a few bugs when it comes to determining the label size. If you’ve encountered a scenario where you specify landscape but it insists on printing in portrait mode or vice versa, then you’ve run into this problem.
  • The fidelity of text is generally poor. The LP2844 only supports a resolution of 203dpi (actually, there’s a 300dpi version out there, but it’s pretty uncommon), so small text can come out blocky, jagged, and hard to read. This isn’t true for the printer’s native fonts, which are optimized for this resolution.
  • GDI has no built-in support for rendering barcodes. Sure, you can write a barcode renderer yourself in GDI (I’ve done this before, but it’s not a trivial task), but why not save some time and use your printer’s built-in functionality?

Grabbing the EPL documentation and Zebra Firmware Downloader

One of the hardest parts of using EPL is simply finding the documentation for it. Zebra provides a very well-written manual, but it’s buried deep on their Web site: here’s a link to a mirrored copy of the EPL programming documentation. While you’re at it, download the Zebra Firmware Downloader, which will help you send your test EPL text files directly to the printer.

Learning about the EPL

An example is usually the easiest way to learn. Here’s the EPL commands necessary to generate the UPC label shown at the top of this blog post:

 
N
q609
Q203,26
B26,26,0,UA0,2,2,152,B,"603679025109"
A253,26,0,3,1,1,N,"SKU 6205518 MFG 6354"
A253,56,0,3,1,1,N,"2XIST TROPICAL BEACH"
A253,86,0,3,1,1,N,"STRIPE SQUARE CUT TRUNK"
A253,116,0,3,1,1,N,"BRICK"
A253,146,0,3,1,1,N,"X-LARGE"
P1,1

EPL is one command per line. A command starts out with a command identifier, typically a letter, followed by a comma-separated list of parameters specific to that command. You can look up each of these commands in the EPL2 programming documentation. Here’s an English-language version of the commands in the above example.

0. Sending an initial newline guarantees that any previous borked
      command is submitted.
1. [N] Clear the image buffer. This is an important step and
      generally should be the first command in any EPL document;
      who knows what state the previous job left the printer in.
2. [q] Set the label width to 609 dots (3 inch label x 203 dpi
      = 609 dots wide).
3. [Q] Set the label height to 203 dots (1 inch label) with a 26
      dot gap between the labels. (The printer will probably auto-
      sense, but this doesn't hurt.)
4. [B] Draw a UPC-A barcode with value "603679025109" at
      x = 26 dots (1/8 in), y = 26 dots (1/8 in) with a narrow bar
      width of 2 dots and make it 152 dots (3/4 in) high. (The
      origin of the label coordinate system is the top left corner
      of the label.)
5. [A] Draw the text "SKU 6205518 MFG 6354" at
      x = 253 dots (3/4 in), y = 26 dots (1/8 in) in
      printer font "3", normal horizontal and vertical scaling,
      and no fancy white-on-black effect.
(6 through 9 are similar to line 4.)
10. [P] Print one copy of one label.

In a way, EPL is quite similar to GDI. You’ve got an image buffer in memory, you issue a batch of commands to doodle and write text and barcodes on that buffer, and then you release the buffer to the printer, telling it to print it.

When designing a label, I find that it’s essential to have a ruler and a calculator handy. You’ll be converting between dots and inches a lot as you design your label. If you find that you send a command (such as a barcode command) and nothing prints out, that usually means that the command was invalid or that some parameter was out of range (for example, the barcode height wasn’t tall enough).

It’d be nice if we could print out these EPL text files that we’ve created in Notepad without writing any code. The Zebra Firmware Downloader (which certainly has a scary name) can do this for us. Once you’ve installed the application, start it up and hunt for the “Auto Detect” button on the toolbar. Once it finds your printer, right-click it in the list, click Select Firmware File…, and browse to the text file containing your EPL commands. Then right-click the printer and choose Download to Selected. The printer will print out your test label. You can now easily be editing your EPL document in Notepad and keep choosing Download to Selected as you iterate through and refine your design.

Printing the label directly from C#

Now that we’ve got the EPL code written, we need to figure out a way for our application to send this file directly to the printer. The easiest way is to use the RawPrinterHelper sample class provided by Microsoft, but we’ll need to fix a bug in it first.

Here’s what a class that prints to the printer might look like:

using System;
using System.Collections.Generic;
using System.Text;
using Skiviez.UndiesClient.Domain;
using Skiviez.Commons.WinForms;
using Skiviez.Commons.Core;
using System.Globalization;
 
namespace BlahBlahBlah
{
    public class UpcLabel
    {
 
        private string upc;
 
        public UpcLabel(string upc)
        {
            if (upc== null)
            {
                throw new ArgumentNullException("upc");
            }
 
            this.upc = upc;
        }
 
        public void Print(string printerName)
        {
            StringBuilder sb;
 
            if (printerName == null)
            {
                throw new ArgumentNullException("printerName");
            }
 
            sb = new StringBuilder();
            sb.AppendLine();
            sb.AppendLine("N");
            sb.AppendLine("q609");
            sb.AppendLine("Q203,26");
            sb.AppendLine(string.Format(
                CultureInfo.InvariantCulture,
                "B26,26,0,UA0,2,2,152,B,\"{0}\"",
                this.upc));
            sb.AppendLine("P1,1");
 
            RawPrinterHelper.SendStringToPrinter(printerName, sb.ToString());
        }
    }
}

This is pretty straight forward. We’re just building our EPL document in a StringBuilder, and we could easily customize the document on the fly with some string.Format calls. Here’s how we might invoke this code:

UpcLabel label = new UpcLabel("603679025109");
label.Print("Zebra  LP2844");

The string that we pass in in the Print function is just the name of the printer as it appears in the Printers list in the Windows control panel. You could pop up a PrintDialog here and ask the user for a printer, but I’ve just hard-coded the name.

The last piece of the puzzle is that RawPrinterHelper.SendStringToPrinter call. Well, it’s the exact same code copy and pasted from the Microsoft support article above. But there’s one bug that we need to fix. In the SendStringToPrinter method, there’s a line that looks like this:

    dwCount = szString.Length;

Change it to look like this:

    dwCount = (szString.Length + 1) * Marshal.SystemMaxDBCSCharSize;

(The main problem is that it wasn’t leaving room for the null character at the end of the unmanaged string, which can cause some mysterious problems with your last command not getting interpreted directly, depending on the length of the raw document. The SystemMaxDBCSCharSize nonsense is for versions of Windows where an ANSI codepage with double byte characters is loaded.)

Conclusions and Delusions

And there you have it! EPL is a fun and simple printer command language, and with a little bit of interop, we can send these commands directly from our C# application. Happy barcoding!