Crowd-sourced POS printer compatibility site is online


Point-of-sale (POS) printers are all different, so the implementation of the more advanced ESC/POS  features varies considerably between vendors and models.

Some months ago I extracted compatibility information out of the escpos-php library, so that it could be used by the python-escpos team as well. We’re still in the early stages, but both of these projects now have similar compatibility features.

You can the new shared database on GitHub as receipt-print-hq/escpos-printer-db, and I’m beginning to add it to some printing projects that need it.

Today I’m blogging a few screen captures from the new web viewer for this database that has come out today, hosted here (source code here).

escpos-printer-db landing page

escpos-printer-db landing page

Character encodings

escpos-printer-db character encoding list

escpos-printer-db character encoding list

escpos-printer-db character encoding detail

escpos-printer-db character encoding detail


escpos-printer-db vendor list

escpos-printer-db vendor list

escpos-printer-db vendor detail

escpos-printer-db vendor detail

Printer profiles

escpos-printer-db printer profile list

escpos-printer-db printer profile list

escpos-printer-db printer profile detail

escpos-printer-db printer profile detail

Tech used

I’ve used materialize.css with backbone.js and jQuery. This is the first time I’ve attempted using materialize – I’m normally a bootstrap.css user.

The back-end data is generated with Python, but the actual site is served as a single-page application, with static JSON files hosting the data.

escpos-php 1.6 released

Another update to the open source receipt printing library escpos-php has been released today. For composer users, it is available as mike42/escpos-php

This is expected to be the final release in the 1.x series. Newer versions will drop support for some end-of-life PHP versions.

The v1.6 release notes detail the changes and lists 13 additional printers tested out by the user base.

Please direct any bug reports to the issue tracker on GitHub. The escpos-php tag on this blog has some tips and examples.

I’ll also call out some related projects that I’ve been involved with, which you should consider contributing to if you are working with thermal receipt printers:

  • escpos-printer-db – Crowd-sourced database of printer support. Add your printer for better support in open source drivers!
  • escpos-tools – Tools to work with ESC/POS binary, including text or image extraction, HTML conversion.
  • chrome-raw-print – A browser plugin to access local printers from a web-page.

mdcheckr: Practical testing for Markdown files

If you are a continuous integration enthusiast, you might be annoyed by preventable errors in project documentation.

I recently found a broken link in one project’s documentation, and a syntax error in an example on another project. I quickly wrote up a simple tool to detect this type of issue, solving the problem for me.

$ mdcheckr
Checking ..
- Code block starting line 21 (language: bash) [ OK ]
- Link [ OK ]
- Link [ OK ]
- Link doc/ [ OK ]
- Link doc/ [ OK ]
- Link doc/ [ OK ]
- Link [ OK ]
- Link [ OK ]
- Image [ OK ]

I disregarded some existing “linter” tools. I don’t believe that it is necessarily productive for small projects to enforce a style-based quality gate for Markdown, but you may be able to configure these tools to be less pedantic. My goal here was to simply churn out a script which could reject objectively broken Markdown files.

I’ve posted this project to GitHub as mdcheckr. It is available as a package for Debian, Ubuntu, and RHEL/CentOS. Travis CI users may be interested to check out this pull request, where mdcheckr is added to the project’s build.

How to access a raw network printer as a file on Linux

I got this interesting question on my blog post ‘Setting up an Epson receipt printer‘:

Wondering if you had an idea on how to map a linux device to the netcat command so that I could “convert” the printer to be a local one? -Marco

I have previously written about the opposite use case: How to use a Raspberry Pi as a print server, where I used netcat to pass data to a local USB printer.

The setup

In this setup, I will assume that you have a working Ethernet printer, which accepts raw data on port 9100.


The aim is to make this appear as a file, so that you can print to it as if it were a local USB printer:


Before you begin

Make sure you have netcat. It’s not used for the real setup, but you will need it for testing. There are a few versions of this tool around, the one used here is:

apt-get install netcat

Verify that your printer accepts text on port 9100 via netcat:

echo "Hello world" | nc -q 1 192.168.x.y 9100

Set up a fake printer on localhost, and leave it running:

nc -klp 9100

Now, test that your fake printer shows output when you send it:

echo "Hello world" | nc -q 1 9100

Why I’m not using..


If you search for how to redirect data to a TCP socket, a common suggestion is:

echo "Hello world" > /dev/tcp/

This is a bash built-in, so unless your use case involves printing from bash, read on.


CUPS does not expose the printer as a file, which is the aim here. It also takes a few seconds to print, so we get much faster results if we remove CUPS from the loop and speak to the printer directly.

Use socat to move data

Socat is capable of making a FIFO file (‘pipe’), and writing this out over the network.

apt-get install socat

Redirect /tmp/my-printer to localhost:9100:

socat PIPE:/tmp/my-printer TCP:localhost:9100

Next, test that you can see the printer file and write to it. This line should appear on your local netcat “fake printer”:

echo "Hello world" > /tmp/my-printer

This file is a “pipe”, so it behaves very similarly to a character device.

If the connection is dropped, socat will exit, and the file will be deleted.

There are some big problems though:

  • If your printer is offline, and you try to print, you will create a regular file at “/tmp/my-printer”, breaking it.
  • This is not a self-restarting service, nor does it start on boot
  • Only the user who runs the command can print

Setting this up as a proper service

The usblp driver allows anybody from the lp group to print, so we will try to do something similar, and get a group-writable pipe:

$ ls /dev/usb/lp0  -Ahl
crw-rw---- 1 root lp 180, 0 Jun 12 12:51 /dev/usb/lp0

We don’t need to run socat as root, so make a new user called fileprint who is in the lp group.

useradd --groups lp fileprint

Add yourself to the lp group as well:

usermod -a -G lp mike

Next, write this systemd service file to /etc/systemd/system/fileprint.service


ExecStartPre=/usr/bin/mkfifo -m '0664' /var/run/fileprint/printer
ExecStart=/usr/bin/socat PIPE:/var/run/fileprint/printer TCP:

What’s going on here?

  • Runs as user fileprint and group lp
  • Manages a runtime directory at /var/run/fileprint
  • Creates a pipe that is group-writeable at /var/run/fileprint/printer
  • Forwards traffic to
  • Re-starts automatically

Load and start the service:

systemctl daemon-reload
systemctl start fileprint
systemctl status fileprint

Test out printing to your local netcat “fake printer”:

echo "Test" > /var/run/fileprint/printer

Note that if you are not in the lp group (check with groups command), you should expect a permission error here — just the same as usblp.

Next, replace with your real printer IP, reload systemd, and restart the service:

nano /etc/systemd/system/fileprint.service
systemctl daemon-reload
systemctl restart fileprint
systemctl status fileprint

Test it again, and your actual printer should print a line of text this time:

echo "Test" > /var/run/fileprint/printer

Finally, enable on boot:

systemctl enable fileprint

When the printer is unplugged, the /var/run/fileprint directory will vanish, so that you can see the missing printer as a “File Not Found” error — just the same as usblp.


This will hold a connection open at all times, which (depending on your printer) may prevent other computers from using it.

The 5890 printer does not support barcodes

I haven’t been able to print barcodes on the 5890 model thermal receipt printer, and it looks like other developers can’t either.

The same printer is sold by several companies. As far as I can tell (mainly from bug reports to escpos-php), these are all the same printer, and could have this issue:

  • AGPtEK SC35-5890F
  • EC Line 5890
  • Gainscha GP-5890
  • HSPOS HS-589C
  • POS-5890
  • WinBond 5890
  • Zjiang ZJ-5890 or POS-5890

In my case, I have a Zjiang POS-5890C, which list the barcode command GS k in its documentation, but does not actually respond to the command.

Separately, a developer has contacted the company and confirmed that this

A few bookmarks for reference:

Testing for barcode support in USB printers

Get yourself a Linux computer or VM, set up the printer with usblp, and run this command:

echo -e '\x1d\x6b\x04000\x00' > /dev/usb/lp0

This command prints a CODE39 barcode containing ‘000’ on printers which support barcodes, and prints garbage on printers which do not.

See also:

libgdx 3D particle effects in HTML


It is not immediately obvious in libgdx why the 3D particle effects don’t work in a HTML target. I’m sharing this snippet for future readers.

In short, the “reflection cache” that is created from the Java code does not include everything required, since many of the classes are only referenced at runtime, when the particle definition is loaded.

These class names are visible in the saved files from the 3D effects editor:

$ cat engine.p | fold -w 80

At runtime, this message is displayed on the web page:

GwtApplication: exception: com.badlogic.gdx.utils.GdxRuntimeException: Could not submit AsyncTask: Error reading file: (filename)
com.badlogic.gdx.utils.GdxRuntimeException: Could not submit AsyncTask: Error reading file: (filename)
Could not submit AsyncTask: Error reading file: (filename)
Error reading file: (filename)
Couldn't find Type for class '$Config'

The reflection is documented on the libgdx Wiki here, and notes that-

  • *.gwt.xml files store this data
  • dependencies defined in the Java are loaded automatically
  • inner classes are also loaded automatically, no need to add them separately

With this in mind, I added the following two lines to the root element in a file called GdxDefinition.gwt.xml

<extend-configuration-property name="gdx.reflect.include" value="" />
<extend-configuration-property name="gdx.reflect.include" value="" />

This did the trick, and the 3D particle feature does indeed work in the libgdx HTML target.

How to add a proper Android Studio launcher on Linux

If you install Android Studio by extracting a Zip file on Linux, it will come without any launch icon. This post assumes that have extracted the install archive to /usr/local and run any recent version of Linux which has Gnome:

unzip android-studio-ide-145.3537/ -d /usr/local

Simply write the following text to ~/.local/share/applications/android-studio.desktop (change paths if your install location differs).

[Desktop Entry]
Comment=Android Studio
Name=Android Studio

Once you save the file, it will appear in your local applications like this:


This can also be added graphically though the Alacarte menu editor if you have it installed.

OpenWrt setup on Netgear WNR2200

I recently wanted to connect some devices for a temporary setup, where a wireless LTE modem would provide Internet access. Unfortunately, one of the devices was not close enough to pick up the signal with its USB WiFi dongle.


Because the modem does not have a LAN port, the usual “run a cable” solution was out. There’s a few other options, from range extenders, to getting better modem, or just upgrading to a “real” USB WiFi dongle. Before purchasing new hardware, I decided to try re-purposing an old Netgear WNR2200 as a wireless client and 4 port switch.


In this setup, the LTE modem does the heavy lifting, with all of the wireless clients using it for LAN and Internet access. In the next room, the Netgear router is placed close enough to pick up the signal, and an Ethernet cable runs to the PC, beyond the reach of WiFi.

Deciding to re-flash

Replacing firmware is worth investigating when the hardware is capable, but you aren’t given the option to configure it the way you want.

The Netgear WNR2200 is a low end wireless router, and the vendor firmware does not support joining a WiFi network as a client.


It also pays to update your research. OpenWrt added support for this router a few days after I bought it, but I hadn’t looked it up again.

Uploading firmware

My main resource was this page on the OpenWRT Wiki. Firmware is organised by wireless chipset, then by router model.

The file I used to update my router was named openwrt-15.05.1-ar71xx-generic-wnr2200-squashfs-factory.img.

This is simply uploaded on the Adminisration → Firmware Upgrade screen:



First impressions

The first thing I noticed was that I lost WiFi, and that the page I had bookmarked for logging in was no longer valid!


This makes sense, of course. The configuration will not be carried across from the vendor firmware, and a different web administration tool is being used.

The Linux userspace is very rich compared with vendor firmware. It has things like dmesg, SSH, ifconfig, ping, and even a networked package manager.

Configuration checklist

I performed all configuration through the web in this setup. The “LuCi” interface allows setting the WiFi chip into “Client” mode, and then searching and joining a network. Once this was done, I assigned it as the “WAN” interface, so that it occupied a single IP address on the WiFi network, and providing a NAT and wired, four port switch.

There are more advanced, bridged setups that are possible. You should investigate this if you want one network, so that things like printer auto-discovery and internal SSH work consistently. I was only interested in sharing the Internet connection, which is why the setup was so simple.

What didn’t work

USB, but I didn’t spend long on this either. I was considering using USB to connect the modem to the Netgear router. The Wiki suggests that this is now possible, but after installing some packages for “USB tethering” and rebooting, I had no luck. Typing lsusb, only the “root hub” was listed, and the device was not getting any power.

This was necessary for the setup, so I just abandoned it. The vendor firmware couldn’t use the USB port for networking either, so no real loss.