Tuesday, August 9, 2016

escposf - A Thermal Printer Filter and Control Command for Linux

escposf - A Thermal Printer Filter and Control Command for Linux

There are millions of thermal printers out there! After recently acquiring a Raspberry Pi 3 computer, I got interested in obtaining and using a matching inexpensive printer. This page provides a short description of a quick hack I ginned up to control printing from the command line or through shell scripts. The command, escposf, simply outputs the requisite control characters to change how the printer prints on the thermal printer. BTW, I first used a thermal printer back in the day when you had to strap a sheet of thermal paper on a metal cylinder, call a remote transmitter, then slam the phone into the rubber cups on the receiving unit - faxes were pretty crude back then!

Note that escposf sends control codes directly to the printer. On the other hand, if you just want a straightforward, easy-to-build CUPS driver, check out the zj-58 filter.  The zj-58 filter rasterizes your document image and sends that image to the printer. I created a LibreOffice Writer document template for use with the zj-58 filter. The template has the following settings: 58mm width, 219mm length, with 0 top and bottom margins and a 2mm left margin and 2.5mm right margin - this works very well, too! As you can see, you can create just about any printout you like, such as tickets, etc.





Using escposf for direct printing is pretty easy. Download the source code and build it!

/*
 * escposf - send or embed printing control data for an ESC/POS format thermal printer.
 *
 * (c)2016 by bball@tux.org for his Raspberry Pi 3 computer's thermal printer
 * Distributed under GNU GPLV3. Get it, read it, use it, love it.
 *
 * note that not all ESC/POS-compatible printers share the same ESC/POS codes!
 *
 * the primary use of this command is to set the printing mode or embed ESC/POS codes
 * into a text stream or file for printing
 *
 * version 0.1 - working bold, underline, alignment, reverse, and 4 types of text types
 * version 0.2 - changed name from escpos_util to escposf (ESC/POS filter)
 *               added font a/b selection, overstrike
 * version 0.3 - added printer initialization (clear buffer, reset to 'standard' mode)
 * version 0.4 - added code cleanup, printer_test shell script to package
 * version 0.5 - removed non-working options (boldface, double-strike)
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

/* additions for command-line processing */
#include <getopt.h>
#include <argp.h>  /* <-- comment out header to build for Mac OS X */

/* command-line help */
const char *version="escposf 0.5";
const char *bug_address="<bball@tux.org>";
static char doc[]="ESC/POS thermal printer command";

void usage() {
printf("%s - %s\n",version,doc);
printf("Control ESC/POS thermal printer output.\n");
printf("Typical usage:\n");
printf("escposf --a 1 | lpr -P lpname -o raw (set text centering)\n");
printf("escposf --f 1 >filename.txt          (embed font selection)\n");
printf("Command-line options are:\n");
printf("[--align n]  [--a n] - align text (0-left, 1-ctr, 2-rght)\n");
printf("[--font n]   [--f n] - select font (0-normal, 1-condensed)\n");
printf("[--init]     [--i]   - reset [initialize] printer\n");
printf("[--rev n]    [--r n] - reverse text (0-off, 1-on)\n");
printf("[--text n]   [--t n] - set text (0-norm, 1-2Xh, 2-2Xw, 3-large)\n");
printf("[--underl n] [--u n] - underline (0-off, 1-1 dot, 2-2 dots)\n");
printf("[--help]     [--h]   - display this message \n");
printf("[--version]  [--v]   - display program version \n");
printf("bug reports, fixes to: %s\n",bug_address);
}

/* control alignment */
int align(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x61); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x61); putchar(0x01); exit(0); break;
        case 2: putchar(0x1b); putchar(0x61); putchar(0x02); exit(0); break;
    }
    printf("ERROR: align option out of range (0-2).\n");
    exit(0); 
}

/* reset, initialize printer */
void init() { putchar(0x1b); putchar(0x40); }

/* control font type (normal, condensed) */
int font(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x4d); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x4d); putchar(0x01); exit(0); break;
    }
    printf("ERROR: font-selection option out of range (0 or 1).\n");
    exit(0); 
}

/* control reverse text */
int reverse(int option)
{
    switch (option) {
        case 0: putchar(0x1d); putchar(0x42); putchar(0x00); exit(0); break;
        case 1: putchar(0x1d); putchar(0x42); putchar(0x01); exit(0); break;
    }
    printf("ERROR: reverse text option out of range (0 or 1).\n");
    exit(0); 
}

/* control underlining */
int underline(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x2d); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x2d); putchar(0x01); exit(0); break;
        case 2: putchar(0x1b); putchar(0x2d); putchar(0x02); exit(0); break;
    }
    printf("ERROR: underline option out of range (0-2).\n");
    exit(0); 
}

/* control text type */
int text(int option)
{
    switch (option) {
        case 0: putchar(0x1b); putchar(0x21); putchar(0x00); exit(0); break;
        case 1: putchar(0x1b); putchar(0x21); putchar(0x10); exit(0); break;
        case 2: putchar(0x1b); putchar(0x21); putchar(0x20); exit(0); break;
        case 3: putchar(0x1b); putchar(0x21); putchar(0x30); exit(0); break;
    }
    printf("ERROR: text option out of range (0-3).\n");
    exit(0); 
}

int main(int argc, char *argv[]) {

    int c = 0;
        static struct option long_options[] = {
            {"align", 1, 0, 'a'},
            {"font", 1, 0, 'f'},
            {"help", 0, 0, 'h'},
            {"init", 0, 0, 'i'},
        {"rev", 1,0, 'r'},
            {"text", 1, 0, 't'},
            {"underl", 1, 0, 'u'},
            {"version", 0, 0, 'v'}
    }; /* end of long options structure */

    while (1) {
        c = getopt_long(argc, argv, "?:a:f:h:i:r:t:u:v:", long_options, 0);
        if (c == -1) {  usage();  break; }
        switch(c) {
            case 'a': align(atoi(optarg)); break;
            case 'f': font(atoi(optarg)); break;
            case 'h': usage(); exit(0); break;
            case 'i': init(); exit(0); break;
            case 'r': reverse(atoi(optarg)); break;
            case 't': text(atoi(optarg)); break;
            case 'u': underline(atoi(optarg)); break;
            case 'v': printf("%s - %s\n",version,doc); exit(0); break;
        case ':': fprintf(stderr, "missing arg\n"); exit(-1); break;
             default: usage(); exit(0);
        } /* end switch */
    } /* end while loop */
  return 0;
}


A simple make will create the command for you. I keep it under /usr/local/bin.

Use the --help, --h, or --? options to get a quick list of commands:

$ escposf
escposf 0.5 - ESC/POS thermal printer command
Control ESC/POS thermal printer output.
Typical usage:
escposf --a 1 | lpr -P lpname -o raw (set text centering)
escposf --f 1 >filename.txt          (embed font selection)
Command-line options are:
[--align n]  [--a n] - align text (0-left, 1-ctr, 2-rght)
[--font n]   [--f n] - select font (0-normal, 1-condensed)
[--init]     [--i]   - reset [initialize] printer
[--rev n]    [--r n] - reverse text (0-off, 1-on)
[--text n]   [--t n] - set text (0-norm, 1-2Xh, 2-2Xw, 3-large)
[--underl n] [--u n] - underline (0-off, 1-1 dot, 2-2 dots)
[--help]     [--h]   - display this message
[--version]  [--v]   - display program version

Instead of going into a long description, here's an example script I use to print a shopping list (helped to get rid of a pad of paper and pencil off the kitchen counter):

#!/bin/sh
# psl - print shopping list on thermal printer

# graphic pathname
GRAPHIC=/home/pi/Desktop/thermal_pics/cat_smile.png

# text list pathname
LIST=/home/pi/Desktop/shoplist

# print custom header
/usr/local/bin/png2escpos $GRAPHIC | /usr/bin/lpr -Pthermie -o raw

# set reverse character printing
/usr/local/bin/escposf -r 1 >/tmp/out.txt

# set 2Xw font
/usr/local/bin/escposf -t 3 >>/tmp/out.txt

echo "*SHOPPING LIST*" >>/tmp/out.txt

# turn off reverse
/usr/local/bin/escposf -r 0 >>/tmp/out.txt

# select normal font
/usr/local/bin/escposf -t 0 >>/tmp/out.txt

# save each line to /tmp file (avoids multiple print jobs)
while read line; do
    echo "$line" >>/tmp/out.txt
done < $LIST

# reset to normal font
/usr/local/bin/escposf -t 0 >>/tmp/out.txt

# print the list
/usr/bin/lpr -Pthermie -o raw /tmp/out.txt

# clean up
/bin/rm -fr /tmp/out.txt


Note that I use the png2escpos utility to print little graphics at the top of the tape!

Oh, and here's a little test script for use with your printer (you'll need to edit it to suit your system):

#!/bin/sh
# ptest - test some escposf commands for a 58mm ESC/POS thermal printer
#
# note: modes are persistent between settings, not power-cycling
#
FILE=/tmp/out.txt

# initialize printer
escposf --i >/tmp/out.txt
# set 'normal' text
escposf --t 0 >/tmp/out.txt
echo "Normal text." >>/tmp/out.txt
echo "ABCDEFGHIJKLMNOPQRSTUVWXYZ" >>/tmp/out.txt
echo "abcdefghijklmnopqrstuvwxyz" >>/tmp/out.txt
echo "0123456789" >>/tmp/out.txt
echo "?&'-@';]{]}/,.$:%^\!&*)(_~" >>/tmp/out.txt

escposf --a 0 >>/tmp/out.txt
echo "Left aligned." >>/tmp/out.txt

escposf --a 1 >>/tmp/out.txt
echo "Centered." >>/tmp/out.txt

escposf --a 2 >>/tmp/out.txt
echo "Right aligned." >>/tmp/out.txt

escposf --a 0 >>/tmp/out.txt
escposf --r 1 >>/tmp/out.txt
echo "REVERSED TEXT" >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt
escposf --t 1 >>/tmp/out.txt
echo "2X high." >>/tmp/out.txt

escposf --r 1 >>/tmp/out.txt
escposf --t 1 >>/tmp/out.txt
echo "Reversed 2X high." >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt

escposf --t 2 >>/tmp/out.txt
echo "2X wide" >>/tmp/out.txt

escposf --r 1 >>/tmp/out.txt
escposf --t 2 >>/tmp/out.txt
echo "Reversed 2X" >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt
escposf --t 3 >>/tmp/out.txt
echo "Large text." >>/tmp/out.txt

escposf --r 1 >>/tmp/out.txt
escposf --t 3 >>/tmp/out.txt
echo "Reversed large" >>/tmp/out.txt

escposf --r 0 >>/tmp/out.txt
escposf --t 0 >>/tmp/out.txt
escposf --u 2 >>/tmp/out.txt
echo "Normal underlined." >>/tmp/out.txt

escposf --u 0 >>/tmp/out.txt
escposf --f 1 >>/tmp/out.txt
echo "Font B." >>/tmp/out.txt
echo "ABCDEFGHIJKLMNOPQRSTUVWXYZ" >>/tmp/out.txt
echo "abcdefghijklmnopqrstuvwxyz" >>/tmp/out.txt
echo "0123456789" >>/tmp/out.txt
echo "?&'-@';]{]}/,.$:%^\!&*)(_~" >>/tmp/out.txt

escposf --t 0 >>/tmp/out.txt
echo "End of test.\n\n\n" >>/tmp/out.txt

/usr/bin/lpr -Pthermie -o raw /tmp/out.txt
/bin/rm -fr /tmp/out.txt

 


I hope you find escposf useful.

4 comments:

  1. I really like these printers and tools like these, great work :)
    What would I need to change to use it for 80mm printers?

    Best regards

    ReplyDelete
  2. if you're using escposf there is no change required - you'll just need to test your printer to determine the best width for the size of font and effect you've selected - note that there is no text wrap but it just works with lpr

    if you know that your printer fully supports ESCPOS (these cheap thermal printers do NOT), you should try the Star or Epson thermal printer drivers for CUPS...

    alternatively, you could hack the zj-58 CUPS driver (source is readily available online)

    regards

    ReplyDelete
  3. for anyone else who needed a refresher on gcc syntax, to compile this, save the source code above as "escposf.c" and run this:

    gcc escposf.c -o escposf

    then you can run it as ./escposf

    ReplyDelete
    Replies
    1. tks, Mr. C! and to be honest, for me, escposf was a fun exercise, but i'm lazy, so now i primarily use LibreOffice w/the zj-58 CUPS driver... :-)

      Delete