Zebra ZPL Print Interceptor

Language: C#
IDE: Visual Studio 2022
Summary: TCP/IP Client, RAW Printing

Sounds dramatic, but app does ‘intercept’ print jobs, converts them into a custom ZPL format and then relays to a designated printer.

Background: A third party software supplier used their own template designer tool to send barcoded labels to Zebra printers It was claimed the print jobs would be ZPL which they technically were, but but in a really poor way.

What it ultimately lead to is the Zebra printer driver rasterizing a barcode into an image. The tool seemingly used a font barcode (guessing 3of9), rather than properly utilising ZPL code to allow the print driver to use code 128 for a more compressed barcode. The resulting oversized barcode (as they were quite long) went off the end of the label and was virtually impossible to change in the tool, rendering the labels useless.

Requirement: Intercept print jobs from the third party system, extract the required data and format into genuine ZPL to be sent to the destination printer.

TCPListener and loops

The app listened for TCP messages using a TcpListener shown below. This was mimicking a printer, so the handshaking was different to the normal ENQ > ACK setup.

C#
// Create a Listener
TcpListener server = new TcpListener(IPAddress.Any, port);
// Start Listening
server.Start();
// Accept a connection, when one is made
client = server.AcceptTcpClient();
// Do this while the connection is open..
while (client.Connected)
{
... // Logic of what to do when a connection is made
}

Within the while loop shown above, a NetworkStream was created to collect any bytes sent to the listening port (see below).

C#
// Create a stream
NetworkStream stream = client.GetStream();
int i;
// Read the intial stream
i = stream.Read(bytes, 0, bytes.Length);
while (i != -1)
{
... // Logic of what to do when data is received
}

These two loops combined created the backbone of a never ending process of receiving data (the original print message), processing it, sending to the printer, then listening for the next set of data.

Printing

As mentioned, once the relevant bits of data had been extracted from the message, it was put together within newly formatted ZPL code and sent to the printer. The code itself was just a text file with placeholders that were replaced by loading the data and then replacing like this;

C#
// Read the lbl file into a variable
ZebraCode = File.ReadAllText("labels\\batchproduct.lbl");
// Replace the placeholders with variables we extracted earlier
ZebraCode = ZebraCode.Replace("{barcode}", barcode);
ZebraCode = ZebraCode.Replace("{hrbarcode}", hrBarcode);
ZebraCode = ZebraCode.Replace("{product}", productName);

The resulting ZebraCode variable was then sent to the printer using the following line of code;

C#
RawPrinterHelper.SendStringToPrinter(destinationPrinter, ZebraCode);

Note: Using ZPL code requires the ability to send RAW print commands to the printer. This was achieved by using the details found on this link here. Many thanks to whoever originally wrote this code, currently unknown, as the link contained within the original question on this request no longer works 🙁

Customisation Without Reprogramming

The app was made fairly maintenance free, by the labels being editable in a notepad application and the all important settings that were required to operate the app were all loaded on startup from a settings file.

This included the designated printer (identified by name), port that was the be listened to, the format of the label (there were only two options either 0 or 1) and the separator used by the incoming ZPL code. It could have done further by adding the label template locations to the settings file too, but maybe that’s for a further release…

ListeningPort=9000
DestinationPrinter=ZDesigner GC420d
LabelType=1
ZPLSeparator=$