Print QR code to network-enabled thermal receipt printer using Java

For more information on the ESC/POS command set which receipt printers use for printing, see What is ESC/POS and How Do I Use It by Michael Billington, also the author of the excellent escpos-php PHP library for receipt printers.

Thanks to this StackOverflow post as well: printing QR codes through an ESC/POS thermal printer? 🙂

Steps to run:

  1. Save the code below as NetworkReceiptPrinterQrcode.java – change QR code content and printer IP accordingly.
  2. Run javac NetworkReceiptPrinterQrcode.java to compile.
  3. java NetworkReceiptPrinterQrcode
  4. If it does not work, try commenting out the command to select the model and removing “model” from the commandSequence array.
import java.io.*;
import java.net.*;
import java.util.HashMap;

/**
 * Print QR Code to network receipt printer
 */
class NetworkReceiptPrinterQrcode {
    public static void main(String argv[]) throws Exception {
        String content = "Test Content";
        String printerIp = "192.168.1.123";
        int printerPort = 9100;

        try {
            Socket socket = new Socket(printerIp, printerPort);
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            DataInputStream in;

            byte[] byteArray = qrCode(content);
            in = new DataInputStream(new ByteArrayInputStream(byteArray));
            while (in.available() != 0) {
                out.write(in.readByte());
            }

            out.writeByte(0x00);
            out.flush();

            out.close();
            in.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Compute bytes for printing QR code
     *     
     * @link Adapted from https://stackoverflow.com/questions/23577702/printing-qr-codes-through-an-esc-pos-thermal-printer/#29221432
     * @param content Contents of QR code
     * @return Bytes for command to send to printer
     */
    public static byte[] qrCode(String content) {
        HashMap commands = new HashMap();
        String[] commandSequence = {"model", "size", "error", "store", "content", "print"};
        int contentLen = content.length();
        int resultLen = 0;
        byte[] command;

        // QR Code: Select the model
        //              Hex     1D      28      6B      04      00      31      41      n1(x32)     n2(x00) - size of model
        // set n1 [49 x31, model 1] [50 x32, model 2] [51 x33, micro qr code]
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=140
        command = new byte[]{(byte) 0x1d, (byte) 0x28, (byte) 0x6b, (byte) 0x04, (byte) 0x00, (byte) 0x31, (byte) 0x41, (byte) 0x32, (byte) 0x00};
        commands.put("model", command);
        resultLen += command.length;

        // QR Code: Set the size of module
        // Hex      1D      28      6B      03      00      31      43      n
        // n depends on the printer
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=141
        command = new byte[]{(byte) 0x1d, (byte) 0x28, (byte) 0x6b, (byte) 0x03, (byte) 0x00, (byte) 0x31, (byte) 0x43, (byte) 0x06};
        commands.put("size", command);
        resultLen += command.length;

        //          Hex     1D      28      6B      03      00      31      45      n
        // Set n for error correction [48 x30 -> 7%] [49 x31-> 15%] [50 x32 -> 25%] [51 x33 -> 30%]
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=142
        command = new byte[]{(byte) 0x1d, (byte) 0x28, (byte) 0x6b, (byte) 0x03, (byte) 0x00, (byte) 0x31, (byte) 0x45, (byte) 0x33};
        commands.put("error", command);
        resultLen += command.length;

        // QR Code: Store the data in the symbol storage area
        // Hex      1D      28      6B      pL      pH      31      50      30      d1...dk
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=143
        //                        1D          28          6B         pL          pH  cn(49->x31) fn(80->x50) m(48->x30) d1…dk
        int storeLen = contentLen + 3;
        byte store_pL = (byte) (storeLen % 256);
        byte store_pH = (byte) (storeLen / 256);
        command = new byte[]{(byte) 0x1d, (byte) 0x28, (byte) 0x6b, store_pL, store_pH, (byte) 0x31, (byte) 0x50, (byte) 0x30};
        commands.put("store", command);
        resultLen += command.length;

        // QR Code content
        command = content.getBytes();
        commands.put("content", command);
        resultLen += command.length;

        // QR Code: Print the symbol data in the symbol storage area
        // Hex      1D      28      6B      03      00      31      51      m
        // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=144
        command = new byte[]{(byte) 0x1d, (byte) 0x28, (byte) 0x6b, (byte) 0x03, (byte) 0x00, (byte) 0x31, (byte) 0x51, (byte) 0x30};
        commands.put("print", command);
        resultLen += command.length;

        int cnt = 0;
        int commandLen = 0;
        byte[] result = new byte[resultLen];
        for (String currCommand : commandSequence) {
            command = (byte[]) commands.get(currCommand);
            commandLen = command.length;
            System.arraycopy(command, 0, result, cnt, commandLen);
            cnt += commandLen;
        }

        return result;
    }
}

[UPDATE 23 JUL 2016]

A friend commented that there was room for refactoring. Replied that I have not touched Java for 15 years and was trying to stick to pure Java, i.e. no external libraries, as I was doing the code as a demo for my Android developer lol. He later sent me a sample of the refactored code, reproduced below. Thanks, Jun Kwang!

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Map;

public class QrCodePrinterRefactored {

    public static void main(String args[]) {
        String content = "Test Content";
        String printerIp = "192.168.1.123";
        int printerPort = 9100;

        try (
            Socket socket = new Socket(printerIp, printerPort);
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(toQrCode(content)));
            DataOutputStream out = new DataOutputStream(socket.getOutputStream())
        ) {
            while (in.available() != 0) {
                out.write(in.readByte());
            }
            out.writeByte(0x00);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Compute bytes for printing QR code.
     *
     * @link Adapted from https://stackoverflow.com/questions/23577702/printing-qr-codes-through-an-esc-pos-thermal-printer/#29221432
     * @param content Contents of QR code
     * @return Bytes for command to send to printer
     */
    public static byte[] toQrCode(String content) {
        return QrCodeBuilder.create()
                .withModel((byte) 0x32, (byte) 0x00)
                .withSize((byte) 0x06)
                .withErrorCorrection((byte) 0x33)
                .withContent(content, (byte) 0x30)
                .toBytes();
    }

    enum QrCommand {
        MODEL, SIZE, ERROR_CORRECTION, STORE, CONTENT, PRINT
    }

    private static final class QrCodeBuilder {

        private final Map commands = new EnumMap<>(QrCommand.class);

        private QrCodeBuilder() {
            // empty
        }

        /**
         * Creates a new builder.
         *
         * @return a builder
         */
        public static QrCodeBuilder create() {
            return new QrCodeBuilder();
        }

        /**
         * Generates a {@code byte[]} array appended with the two {@code byte} arguments.
         *
         * @param penultimate the penultimate byte
         * @param last        the last byte
         * @return a {@code byte[]} array starting with (@code 0x1d 0x28 0x6b 0x03 0x00 0x31)
         */
        private static byte[] append(byte penultimate, byte last) {
            return new byte[]{0x1d, 0x28, 0x6b, 0x03, 0x00, 0x31, penultimate, last};
        }

        /**
         * Sets the model with the following bytes: {@code 0x1d 0x28 0x6b 0x04 0x00 0x31 0x41 n1(x32) n2(x00)}.
         *
         * @param model [49 x31, model 1] [50 x32, model 2] [51 x33, micro qr code]
         * @param size  (x00 size of model)
         * @return this builder
         */
        public QrCodeBuilder withModel(byte model, byte size) {
            commands.put(QrCommand.MODEL, new byte[]{0x1d, 0x28, 0x6b, 0x04, 0x00, 0x31, 0x41, model, size});

            return this;
        }

        /**
         * Sets the size with the following bytes: {@code 0x1d 0x28 0x6b 0x03 0x00 0x31 0x43 n}.
         *
         * @param size depends on the printer
         * @return this builder
         */
        public QrCodeBuilder withSize(byte size) {
            commands.put(QrCommand.SIZE, append((byte) 0x43, size));

            return this;
        }

        /**
         * Sets the error correction with the following bytes: {@code 0x1d 0x28 0x6b 0x03 0x00 0x31 0x43 n}.
         *
         * @param errorCorrection [48 x30 -> 7%] [49 x31-> 15%] [50 x32 -> 25%] [51 x33 -> 30%]
         * @return this builder
         */
        public QrCodeBuilder withErrorCorrection(byte errorCorrection) {
            commands.put(QrCommand.ERROR_CORRECTION, append((byte) 0x45, errorCorrection));

            return this;
        }

        /**
         * Sets the content.
         *
         * @param content the content to set
         * @param print   the print byte
         * @return this builder
         */
        public QrCodeBuilder withContent(String content, byte print) {
            // QR Code: Store the data in the symbol storage area
            // Hex      1D    28    6B    pL    pH    31            50            30            d1...dk
            // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=143
            //          1D    28    6B    pL    pH    cn(49->x31)   fn(80->x50)   m(48->x30)    d1...dk
            int contentLength = content.length() + 3;
            byte length = (byte) (contentLength % 256);
            byte height = (byte) (contentLength / 256);

            commands.put(QrCommand.STORE, new byte[]{0x1d, 0x28, 0x6b, length, height, 0x31, 0x50, print});
            commands.put(QrCommand.CONTENT, content.getBytes());
            commands.put(QrCommand.PRINT, append((byte) 0x51, print));

            return this;
        }

        public byte[] toBytes() {
            if (commands.size() != QrCommand.values().length) {
                throw new IllegalStateException("Not all commands given.");
            }

            byte[] result = new byte[0];
            for (byte[] current : commands.values()) {
                result = Arrays.copyOf(result, result.length + current.length);
                System.arraycopy(current, 0, result, result.length, current.length);
            }

            return result;
        }
    }
}