Cover V10, I05
Article
Figure 1
Listing 1
Listing 2
Listing 3
Listing 4
Listing 5
Listing 6
Listing 7

may2001.tar


Communicating with Intermec and Zebra Bar Code Printers

Ed Schaefer

If you're administering a UNIX application requiring bar code printer support, plan on serious development time. Every programming example I've seen is DOS/Windows based. In this article, I will demonstrate a UNIX "C" tool kit for communicating with Intermec and Zebra bar code printers, both serial and TCP/IP network connected. Intermec and Zebra are two premier providers of quality bar code printers.

The demonstration program, bc_demo, uses the tool kit to create an Intermec and Zebra test label containing human-readable text, code 39 bar codes, and the ubiquitous Consumer Electronics (CE) graphics character. Figure 1 shows the example label.

In normal production, required data such as labels, serial devices, network ports, and data sent to the bar code printer are stored in a database, a file system, or other repository and retrieved as needed. Because the emphasis is on using the tool kit, these objects are hard-coded in a "C" structure and arrays of character pointers to strings and are not retrieved.

How Do Bar Code Printers Work?

The printer vendors provide a control language for controlling the printer's firmware downloading a label format to the printer's RAM. The label format is the definition of what objects appear on the label. For a given object, the label defines the font and size, location, human readable or bar code, or whether the field data is constant or variable. Also, user-defined graphics characters, such as the CE mark, may also be downloaded to the printer.

Once a format exists in the printer's RAM, the control language downloads the required variable data for each required label and prints the label. Typically, a format downloads to the printer once and is used many times.

For Intermec and Zebra printers, the control language is the Intermec Printer Command Language and the Zebra Programming Language II (ZPL II), respectively. A detailed explanation of these languages is beyond the scope of this article. Just enough control language is used to download and print the example label in Figure 1.

What is the Environment?

The tool kit was developed under Solaris 7 (11/99) using an Intel Dual Xeon System with an Intel Pro100+ network adapter. Because the operating system is Solaris, the network communication will be decidedly BSD. The printers are the Intermec 3400B (200 dpi) and the Zebra 105Se (203 dpi). While the tool kit functions with any printer supporting either of the two control languages, the demo label's real estate (i.e., where the objects reside on the label) is designed for the above printers.

How Does the bc_demo Program Work?

In a production environment, the label format and graphics characters are downloaded once to the printer. From that point, for each label required, the variable data will be collected, and in one step, the data and commands will be downloaded, and the label will be printed. Emulating production, the demo program (bc_demo) downloads the required format and prints two labels with differing data. The pseudo code is:

1. Choose the printer (Intermec TCP/IP, Zebra TCP/IP, Intermec Serial, or Zebra Serial).

2. Open the network port or serial device based on the chosen printer.

3. Download the CE graphic character to the printer.

4. Download the label format to the printer.

5. Download the data to the printer for label 1 and print label 1.

6. Download the data to the printer for label 2 and print label 2.

7. Close the network port or serial device.

How Do I Choose a Printer?

Via the command line, choose the printer to demo. The keyword intermectcpip is the Intermec network printer; zebratcpip is the Zebra network printer; intermecserial is the Intermec serial printer; and zebraserial is the Zebra serial printer.

In the structure declaration section of Listing 1 (printer_choice), declare the four keyword definitions: the function to call using a pointer to a function, (*fptr()), and the three arguments to the function. The arguments are the network address or serial device, the port number of the network device or baud rate of the serial device, and the printer choice (1 = Intermec, 2 = Zebra).

Assign the address of the function to call -- either bc_tcpip or bc_serial -- to the pointer to a function. At runtime, the loop processing the command-line options determines the correct function to call based on printer choice. The NULL pointer at the end of the program structure terminates the loop. The functions bc_tcpip and bc_serial must contain the same argument types and return the same data type.

How Do I Use a Serial Printer?

If the choice is a serial printer, the bc_serial function in serial.c (Listing 2) executes with the following parameters: which serial port to open, the baud rate (9600 in the example), and which serial printer to use (Intermec = 1 and Zebra = 2).

In UNIX, a device can be opened just as a file is opened. The open function opens the device and returns the operating system's lowest available integer file descriptor. The bitwise inclusive (OR or O_RDWR and O_SYNC) opens the device for read-write and causes data to be delivered to the device to block.

Once the file descriptor is obtained, a call to the set_terminal function manipulates printer options using the termio structure and the ioctl control device function:

1. The terminal input control flag, termio.c_iflag, with an inclusive OR sets the ignore break condition, (IGNBRK), inputs parity checking (INPCK), strips characters to 7 bits (ISTRIP), and enables start/stop control (IXON|IXOFF).

2. The terminal control mode flag, termio.c_cflag, bitwise inclusive OR sets the baud rate (B9600 or B19200), enables receiver (CREAD), 7-bit character size (CS7), and enables parity (PARENB).

3. The terminal special control character array, termio.c_cc[VMIN] = 1 and c_cc[VTIME] = 0, controls processing just one character at a time.

The original terminal values were saved before calling set_terminal. When the label printing is completed, the original terminal values are restored.

How Do I Communicate with the Printer?

Once the printer is opened, write objects to the device using the write_load_commands function, located in bc_demo.c. The write_load_commands parameters are the array of character pointers comprising the data and the integer file descriptor returned from the open function call or from a network socket call. For each line of text in the array, you can call the write_port_data function, also located in bc_demo.c, to write the string to the printer. The write_port_data function writes the passed string one character at a time up to the null terminator.

Any array of pointers passed to the write_load_commands function must be terminated with a null character pointer, (char *) NULL.

How Do I Download a Graphics Character?

The first object to download is the CE graphics character located in cemarks.h, Listing 3. The array of character pointers containing the Zebra CE definition is *zebra_ce[] and the Intermec definition is *intermec_ce[]. Download the correct array based on which printer is chosen.

Intermec and Zebra each have their own method for creating graphics characters from bit-mapped objects. While the methods are beyond this article's scope, Intermec calls their characters 6-bits per byte graphics and Zebra refers to their graphics as hexadecimal representations.

Besides the obvious differences of graphics definition and command-language syntax, a few of the differences between the two vendors are:

1. Zebra requires the graphics character to be downloaded as a one-character stream, while the Intermec supports writing multiple lines.

2. Zebra identifies the character by name (i.e., CEMARK.ZPL) while Intermec identifies the character by graphics position in the printer (i.e., G0).

3. Intermec can, optionally, embed a graphics character definition within a normal label format and download both at the same time. Zebra requires graphics characters to be downloaded separately from the label format.

4. Intermec embeds the command to print a previously downloaded graphics character within the label format, while Zebra embeds the print graphics command with the other print commands.

How Do I Download a Label Format?

The Intermec and Zebra label formats are stored in intermec.h, Listing 4, and zebra.h, Listing 5, respectively. As with the graphics characters, labels are stored in an array of character points with the Intermec format stored in *load_intermec_label[] and the Zebra format stored in *load_zebra_label[]. Via a call to the write_load_commands function, you can download the correct array based on the chosen printer.

How Do I Print the Label?

Once the graphics character and label format are downloaded, download the data and print commands to the printer and the label prints. The Intermec and Zebra data and print commands are also stored in intermec.h and zebra.h respectively. The first Intermec label is *print_intermec_label1[], and the second is *print_intermec_label2[]. The Zebra data and print commands are named similarly in zebra.h. Via a call to write_load_commands function, download the correct array based on the chosen printer and the label prints.

If the choice is to use a TCP/IP network printer instead of a serial printer, follow the same downloading and printing scenario as with the previously described serial printer, only replace the serial open with a TCP/IP network open.

How Do I Use a TCP/IP Network Printer?

To use the TCP/IP printer, the bc_tcpip function in tcpip.c (Listing 6) executes with three parameters: the network address to open, the network port (3001 for Intermec and 9100 for the Zebra), and the network printer (Intermec = 1 and Zebra = 2).

The open_network_device function, using the Berkeley Network Library Routines, opens the printer port and returns a socket descriptor analogous to the file descriptor when opening a file:

1. Assuming the printer address is properly set up in /etc/hosts, resolve the printer address by calling the gethostbyname library function returning a pointer to a hostent structure.

2. To perform the network I/O, create a socket descriptor by calling the socket library function returning an integer socket descriptor. The arguments to the socket function are the AF_INET constant to designate the Internet protocol and the SOCK_STREAM constant defining a stream socket.

3. Before making a connection to the printer, create a socket structure of type sockaddr_in:

A. Initialize the socket with a bzero function call.

B. Set the socket family to the AF_INET constant designating the Internet protocol.

C. Set the socket address to the hostent address determined in Step 1.

D. Set the socket port number and verify proper byte ordering with the htons library function.

4. Establish the TCP/IP connection using the library function connection. The arguments to connection function are the socket descriptor determined in Step 2 and the address of the socket structure determined in Step 3.

5. Return the socket descriptor to the bc_serial function and continue processing.

How Do I Compile and Link bc_demo?

Under Solaris, the TCP/IP program tcpip.c, requires a different C compiler from the default to compile and link correctly. The Makefile (Listing 7) will compile and link the bc_demo project correctly.

Conclusion

This article has presented a tool kit for communicating with serial printers. I'd like to conclude by noting the following issues:

1. A two-second sleep command exists after both the serial and network open because a small amount of time is required to ensure the device is ready to accept input. Production code should provide a more foolproof method than a sleep.

2. The serial part of the tool kit is designed using the Solaris serial ports. If you're using a port server on a different UNIX variant, I do not guarantee the tool kit will function correctly. If trouble arises using a port server, experiment with the open function parameters. As a last resort, download the data in the structures in cemarks.h, intermec.h, and zebra.h to an ASCII file and concatenate the contents of the file to the serial port device using the UNIX cat command.

3. The Intermec network printer setup requires a step not documented. Point your browser to the Intermec printer address (both the Intermec and the Zebra bar code printers have their own home page). After logging in as administrator, change the following:

A. Check the "Reverse Telnet Options Enabled" box.

B. Set the Printer 1 Reverse Telnet Port Number to the proper port number (3001 in this case).

References

Austin, Lori. Intermec 3400 Bar Code Label Printer User's Manual, Intermec Corporation, 1995.

ZPL II Programming Guide, Zebra Technologies Corporation, 1998

Stevens, W. Richard. Unix Network Programming, Prentice-Hall, Inc. 1990.

Acknowledgements

The word "Intermec" is a registered trademark of Intermec Corporation. The word "Zebra" is a registered trademark of Zebra Technologies Corporation.

Ed Schaefer is a frequent contributor to Sys Admin. He is a Senior Programmer Analyst for Intel's Factory Integrated Information Systems (FIIS) Group of Aloha, OR. His viewpoints on bar code printing or any other subject do not reflect Intel's position.