Getting a USB receipt printer working on Linux

In this post, I’ll step through how to get a thermal receipt printer with USB interface appearing on Linux. The aim of this is to be able to point a driver such as escpos-php at the device. The printer used here is an Epson TM-T20, which is very common in point-of-sale environments.

I have previously written quite a bit about how to use thermal receipt printer protocols, but the previous printer I covered had only a network interface, not USB like this one:


The directions below are for Debian, but could be adapted for any other Linux.

Find the device file

Plug in your printer, and check that usblp sees it:

[12724.994550] usb 8-4: new full-speed USB device number 5 using ohci-pci
[12725.168956] usb 8-4: New USB device found, idVendor=04b8, idProduct=0e03
[12725.168963] usb 8-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[12725.168968] usb 8-4: Product: TM-T20
[12725.168971] usb 8-4: Manufacturer: EPSON
[12725.168975] usb 8-4: SerialNumber: ....
[12725.175114] usblp 8-4:1.0: usblp1: USB Bidirectional printer dev 5 if 0 alt 0 proto 2 vid 0x04B8 pid 0x0E03

This kernel module makes your printer visible as a device file, so that it can be accessed in the old-fashioned way. Find the new device file under /dev/usb:

ls /dev/usb

In my case, this was /dev/usb/lp1. The next step is to see if you can write to it:

echo "Hello" >> /dev/usb/lp1

Chances are, you will get a permission denied error at this point, so find out what group the printer is in:

stat /dev/usb/lp1

Which will show output something like:

File: ‘/dev/usb/lp1’
  Size: 0         	Blocks: 0          IO Block: 4096   character special file
Device: 5h/5d	Inode: 220997      Links: 1     Device type: b4,1
Access: (0660/crw-rw----)  Uid: (    0/    root)   Gid: (    7/      lp)

This file is owned by group lp (“line printer”). If your username was bob, you would add yourself to this group using:

sudo usermod -a -G lp bob

If you plan to build a web-based point-of-sale system with this, then also add the www-data user to that group.

Now log out and back in, and the previous test should now be working:

echo "Hello" >> /dev/usb/lp1

Troubleshooting: Check usblp

If these steps don’t work, then your computer ether doesn’t have, or isn’t using usblp You’ll need to check a few things:

  • Install a different linux-image if the driver is not on your computer at all.
  • modprobe or insmod usblp
  • blacklist a vendor driver which has claimed the interface.
    • run lsusb -v and usb-devices (look for driver=)

Printing something useful

As a duplicated section from my earlier post, the printer uses ESC/POS, which means it accepts plaintext with some special commands for formatting.

A simple receipt-generator, foo.php, might look like this:

/* ASCII constants */
const ESC = "\x1b";
const GS="\x1d";
const NUL="\x00";

/* Output an example receipt */
echo ESC."@"; // Reset to defaults
echo ESC."E".chr(1); // Bold
echo "FOO CORP Ltd.\n"; // Company
echo ESC."E".chr(0); // Not Bold
echo ESC."d".chr(1); // Blank line
echo "Receipt for whatever\n"; // Print text
echo ESC."d".chr(4); // 4 Blank lines

/* Bar-code at the end */
echo ESC."a".chr(1); // Centered printing
echo GS."k".chr(4)."987654321".NUL; // Print barcode
echo ESC."d".chr(1); // Blank line
echo "987654321\n"; // Print number
echo GS."V\x41".chr(3); // Cut

And you would send it to the printer like this:

php foo.php > /dev/usb/lp1

Scaling this up

The codes are quite tricky to work with manually, which is why I put together the escpos-php driver. You can find it at:

The above example would be written using escpos-php as:

require __DIR__ . '/autoload.php';
use Mike42\Escpos\Printer;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
$connector = new FilePrintConnector("/dev/usb/lp1");
$printer = new Printer($connector);

/* Print some bold text */
$printer -> setEmphasis(true);
$printer -> text("FOO CORP Ltd.\n");
$printer -> setEmphasis(false);
$printer -> feed();
$printer -> text("Receipt for whatever\n");
$printer -> feed(4);

/* Bar-code at the end */
$printer -> setJustification(Printer::JUSTIFY_CENTER);
$printer -> barcode("987654321");
$printer -> cut();

This would be sent to the printer by loading it from the web, or running the script on the command-line:

php foo2.php

Howto: Tethered photo capture on Linux

Tethered capture diagram

Have you ever wondered how professionals get photos to pop up on their computer as they snap them? Most higher-end cameras have mini USB connection, and software is available to retrieve images as they are taken.

Rather than use a GUI app, in this post I’ll use a command-line program called gphoto2 to drop the images into a folder. With large thumbnails set in your file browser, a desktop program would be redundant.

First, you need to install the program. Depending on your system, one of the following commands should do the trick:

apt-get install gphoto2
yum install gphoto2

Now, plug in the camera. The command to do a “tethered capture” is:

gphoto2 --capture-tethered

Unfortunately, in most desktop environments, your file manager will mount the camera automatically. If this is the case, then the command will give you an error:

mike@mikebox:~$ gphoto2 --capture-tethered
Waiting for events from camera. Press Ctrl-C to abort.                         

*** Error ***              
An error occurred in the io-library ('Could not claim the USB device'): Could not claim interface 0 (Device or resource busy). Make sure no other program (gvfs-gphoto2-volume-monitor) or kernel module (such as sdc2xx, stv680, spca50x) is using the device and you have read/write access to the device.
*** Error (-53: 'Could not claim the USB device') ***       

For debugging messages, please use the --debug option.
Debugging messages may help finding a solution to your problem.
If you intend to send any error or debug messages to the gphoto
developer mailing list , please run
gphoto2 as follows:

    env LANG=C gphoto2 --debug --debug-logfile=my-logfile.txt --capture-tethered

Please make sure there is sufficient quoting around the arguments.

Simply find the camera and unmount it using the eject button:

Eject the camera storage

Now when you type the command, it will block until a photo is taken, and then show you the name of the photo:

mike@mikebox:~$ gphoto2 --capture-tethered
Waiting for events from camera. Press Ctrl-C to abort.                         
UNKNOWN PTP Property 5007 changed
Downloading 'DSC_0236.JPG' from folder '/store_00010001/DCIM/100NCD90'...
Saving file as DSC_0236.JPG                                                    
Deleting 'DSC_0236.JPG' from folder '/store_00010001/DCIM/100NCD90'...

Each of the photos is loaded into the working directory after you release the shutter, so you simply close gphoto2 when you’re done — no manual downloading or SD card required.

Extending this method

Ok, so now that I’ve covered this basic use case, the real reason I suggest gphoto2 is that it will let you script just about anything to do with your camera.

Just typing gphoto2 --help shows that it can let you trigger a photo or video on a timer, download and delete folders from the camera, or hook up programs via a pipe for processing the files in realtime.

Endless possibilities.

Productivity: De-activate social networking on a schedule with cron

Sometimes you need to block out distractions for a set amount of time. One of these is social networking, and it’s fairly early to temporarily break it (all for productivity of course!).

Mac or Linux users can break like this:

echo "" >> /etc/hosts

This tells the computer that it hosts, so it wont load it from the internet.

And to compliment this, we have sed, which can delete lines from a file in-place based on a pattern:

sed -i '/' /etc/hosts

Replace above with your social network of choice, and have a shot. It will take a few minutes to have an effect, because of open connections and your computer’s DNS cache.

Scheduling it in

Cron is the go-to solution for scheduling any command on Unix. We’ll run this as root, as normal users don’t have permission to edit /etc/hosts:

crontab -e

If prompted, select nano as an editor.

If, for example, 6pm — 8pm weekdays is a distraction-free time for you, you would schedule the first command for 18:00 on days 1-5, and the second for 20:00 on days 1-5:

# m h  dom mon dow   command
0 18 * * 1-5 echo "" >> /etc/hosts
0 20 * * 1-5 sed -i '/' /etc/hosts

If your use case calls for something more advanced, consider learning how to use squid to manage web traffic.

When you break something..

If you accidentally delete your /etc/hosts while experimenting, you can fetch its contents from /var/lib/dpkg/info/netbase.postinst (source).

QJoyPad update

As of April, there is now a qjoypad package available in the official Debian repository, see This program allows you to map joystick events to keyboard and mouse actions. This means that you could use, for example, a USB gamepad to browse the web.

I have previously blogged about the lack of usable, packaged programs to do this, and highly recommend getting your hands on a USB gamepad and giving this a shot. Depending on your distribution, you will soon be able to just run:

sudo apt-get install qjoypad

Then connect the gamepad and type:


You then get a window which shows you what buttons you can map:

Clicking a button prompts you to press a key on your keyboard or select a mouse button, and each profile can be saved and named for later use.

As far as small utility programs go, I now rely on this about as much as the Gnome calculator.

An alternative: AntiMicro

I’ll also give a mention to AntiMicro, which has similar goals, and looks very promising.

As a bit of background, there is a limitation in QJoyPad around adding modifier keys: For example, you simply can’t map a joystick button to <Ctrl>+Q, making it inefficient to use with most applications. The author of AntiMicro, Travis, sent me an email back in September to point out that his program both solves this issue, and has a Debian package available for download.

Whilst I was quite impressed with the functionality in AntiMicro, I found it a whole lot more complex to set up. I think this would be more useful for gamers, as mouse acceleration and key repeats are overkill for my use case (browsing the web and controlling VLC, MythTV, etc).

So, if you are a power user, then I suggest you give it a shot. Power users with apt-get may also consider joy2key, which is the best non-GUI tool for this, and is available in the Debian repositories.

A tour of ReactOS 0.3.15

ReactOS is a project which aims to create an open source operating system which is binary-compatible with Windows. Although it is still cautiously labelled “alpha”, its basic use is about as reliable as Windows once was.

This post runs through the steps to install ReactOS 0.3.15 as a KVM guest on Linux.


Before attempting anything, check that you a CPU supports Intel VT or AMD-V. This command will return the number of CPU cores with svm or vmx flags:

cat /proc/cpuinfo | grep -E 'svm|vmx' | wc -l

Now download the ReactOS 0.3.15 disk from, extract it to get the .iso, and fetch some packages if you don’t have them installed:

apt-get install libvirt-bin kvm qemu-utils

Prepare a disk image to install to. If your hardware is slower, then a raw image is a better idea than the qcow2:

qemu-img --help
qemu-img create -f qcow2 reactos.img 4G

The working directory now has:

mike@mikebox:~/vm/reactos$ ls -Ahl
total 77M
-rw-r--r-- 1 mike mike  77M May 19  2013 ReactOS-BootCD.iso
-rw-r--r-- 1 mike mike 193K Jan 30 21:05 reactos.img

Installation and first boot

The kvm command will pop up a window with the guest operating system. To boot from the install disk, run:

kvm -hda reactos.img --cdrom ReactOS-BootCD.iso -vga std -localtime -net nic,model=ne2k_pci -net user

The meaning of each of these options is:

-hda reactos.img
Sets the HDD image file.
--cdrom ReactOS-BootCD.iso
Sets the CDROM image file. Because reactos.img is blank, this will boot.
-vga std
Sets the VGA card.
Emulates a system clock in local time, rather than UTC.
-net nic,model=ne2k_pci
Sets the network card to something ReactOS will recognise.
-net user
Enables user-mode networking. Your computer will emulate a network and pass on TCP and UDP connections. This is the easiest mode to use, but ICMP packets (such as pings) will not work, and the VM will not be accessible from other computers.

Installation was fast, error-free, and did not require a network connection. The first screen capture below was taken at 16:04:49, and the desktop was captured at 16:06:07 (1 minute 18 seconds later). Most of that time would have been wasted waiting for user input.

ReactOS installer language select
ReactOS install or repair
ReactOS installer disclaimer
Confirm install settings
Select partition
Formatting options for new partition
Formatting confirmation dialog
ReactOS install directory
ReactOS installer copying files
Bootloader options (freeloader)

After copying files, the installer reboots to a more user-friendly mode (similar to the Windows installer):

ReactOS boot menu
ReactOS loading NTOSKRNL.EXE
ReactOS boot screen
ReactOS Install - Installing devices
ReactOS Install - Welcome to the ReactOS Setup Wizard
ReactOS Install - Acknowledgements
ReactOS Install - Personalize your Software
ReactOS Install - Computer name and Administrative Password
ReactOS Install - Regional settings
ReactOS Install - Date and Time
ReactOS Install - Registering Components
ReactOS Install - Completing the ReactOS Setup Wizard
ReactOS desktop after installation

The installed system

After installation, the --cdrom option can be dropped:

kvm -hda reactos.img -vga std -localtime -net nic,model=ne2k_pci -net user

The first thing I did was correct the colour depth, and then attempt to install VLC. This did not turn out well (the console screen is QEMU-monitor):

Reactos display properties
VLC installation in ReactOS Applications Manager
Ctrl+Alt+Del from QEMU monitor
BSOD during VLC installation

I used command prompt to verify that networking was fine (note the lack of ICMP in user-mode networking):

Ping from command prompt with user-mode networking as KVM guest
Testing network on ReactOS

The Firefox 22 install worked, but it went awry after that. Several reboots later I gave up:

Firefox installer on ReactOS
Firefox frozen on startup
BSOD while running firefox on ReactOS

The built-in programs were much more usable:

ReactOS paint
ReactOS paint Save As dialog
ReactOS Explorer
ReactOS Start Menu showing Administrative Tools
ReactOS Device Manager

PuTTY installed flawlessly, and I was able to SSH to the host computer:

PuTTY installation on ReactOS
PuTTY readme in ReactOS notepad
Main PuTTY window after installation
Using command prompt to find host computer address:
PuTTY with host computer address
PuTTY connected to host computer, showing 'uname -a' output

An example of a frozen program causing graphics glitches (Windows up to XP does this as well):

Frozen Application Manager in ReactOS

And an obligatory screenshot of the “Properties for System” dialog, showing the build as 20130518-r59037:

Reactos 'Properties of System' dialog 20130518-r59037


ReactOS is a cool idea and project, but the OS is still very glitchy. The built-in apps are stable and familiar-looking, but you would require a lot of patience (and a lot of rebooting) to use a ReactOS system for more than a few minutes.

Being open source is a big plus, as there is no need to activate the installation or enter software keys. GNU/Linux users will already be accustomed to this.

Recovering auto-saved files in MySQL Workbench

MySQL workbench is an open source tool for designing databases. As version 6.0.8, it is one of those programs where you need to save often, because the window you are working in will vanish every couple of hours.

Bug #1: Can’t recover files that weren’t saved

I was unlucky enough to have forgotten to save my work when it crashed today, and found this nasty flaw in the auto-recover feature:

Auto-save model interval: An open model that has not been saved will automatically be saved after this period. On loading a model file, MySQL Workbench will notify the user if the file was not previously saved correctly, due to a crash or power failure. MySQL Workbench can then attempt to recover the last auto-saved version. For automatic recovery to be available for a new file, it will have to have been saved at least once by the user.

Uh oh! The file hadn’t been saved yet, so it’s gone right? According to wb_model_file.cpp, this is not the case. The auto-save file is always written, but the recovery process wont be started until you try to use it again (which will never happen if you don’t have an old saved version):

/* Auto-saving
 * Auto-saving works by saving the model document file (the XML) to the expanded document folder
 * from time to time, named as document-autosave.mwb.xml. The expanded document folder is
 * automatically deleted when it is closed normally.
 * When a document is opened, it will check if there already is a document folder for that file
 * and if so, the recovery function will kick in, using the autosave XML file.

So under ~/.mysql/workbench/, I found a newmodel.mwbd folder. Workbench files are .zip files in disguise, so I compared it to a test file. It had all the same content, but with a document-autosave.xml, rather than a document.xml (see test file below):

Test archive

Cool, so I’d just rename the file, compress the whole lot and make it a .mwb? No such luck.

Bug #2: File Roller can’t compress ‘@’ files

Possibly because of the -@ command-line option in the zip command, File Roller refused to work with these files.

File Roller bug

Luckily, the document.mwb.xml file alone is enough for the file to be recognised and recovered from the auto-saved files:

File is recovered

The take-away from this? Save your work. In 2014, you still can’t count on auto-save to do this!

Scripted screen captures

This is a script I put together for capturing a window’s contents as it changes, because “Print Screen and crop” gets old very quickly!

It saves me a lot of time when working in virtual machines or creating user docs, as it means that every step (and error message) is captured.

The commands used are in the x11-utils and netpbm and x11-apps packages on Debian.

sudo apt-get install x11-utils netpbm x11-apps

echo "Click a window to start capturing it."
window=`xwininfo -int | grep 'Window id:' | cut -d' ' -f4`
echo -n "Capturing window $window"
empty=`echo -n "" | md5sum | cut -d' ' -f1`
echo -n "" > $captured
while [ "$prev" != "$empty" ]; do
	md5sum=`xwd -id $window 2> /dev/null | xwdtopnm 2> /dev/null | md5sum | cut -d' ' -f1`
	if grep -Fxq "$md5sum" "$captured"
		echo -n "."
		if [ "$md5sum" != "$empty" ]
			echo $md5sum >> $captured
			echo ""
			echo -n $md5sum
			file=`date --iso`-capture-`printf "%03d" $i`.png
			xwd -id $window | xwdtopnm 2> /dev/null | pnmtopng 2> /dev/null > $file
	sleep 1
echo ""
echo "Empty screen capture received. Quitting. (did you close the window?)"


If you wanted to document a “Malformed Expression” error in Gnome Calculator, you can run and then demonstrate it:

$ ./
Click a window to start capturing it.
Capturing window 31457283
Empty screen capture received. Quitting. (did you close the window?)

The lines are checksums of PNM data, and the dots are times when no screenshot was saved. This guarantees that each file in the output is unique:

Example of captured files

The captured.txt file simply contains the list of checksums, and is useless after the script terminates.

Brother HL-2270DW on Linux

The Brother HL-2270DW is one of the best budget laser printers around, with third party consumables readily available on eBay.

The printer has a network port on the back of it, which is great news for GNU/Linux users, because networked printers tend to speak standard protocols.

The fastest way to get this printer working without Windows is to plug it in and log in via the web. The default settings have DHCP enabled. Here are a few pieces of crytic voodoo magic that helped me:

  1. To reset the print server settings, hold “GO” on startup, then let go and press it 6 times.
  2. The default admin login is admin / access
  3. On my printer (Firmware 1.10), printing network settings by holding “GO” for 10 seconds caused the network card (Brother NC-7800w) activity lights to go off, requiring a reboot.

On Debian, there is no “Brother HL-220DW” CUPS driver on the list, but I found the following driver to work fine (and allow duplex):

Brother HL-2170W Foomatic/hpijs-pcl5e (recommended)

To use this driver, you will need to install the HP Linux Printing and Imaging printer driver:

apt-get install printer-driver-hpijs

Missile Launcher on Raspberry Pi

This post covers a few setups to experiment with if you have a DreamCheeky USB missile launcher and a Raspberry Pi.

A newer version is being sold on ThinkGeek, but the one I used was:

DreamCheeky USB Missile Launcher

Setup 1: Direct to PC

The launcher comes with some software to let you connect it straight to a computer. Of course, USB can only go 5 metres, which is not much fun for cubicle warfare:

USB Missile Launcher setup with PC

I included this setup because it is the easiest way for Debian/Ubuntu users to test that they can use this driver, which is needed for the other setups.

Setup 2: Networked with Raspberry Pi

So for this setup, you need a Raspberry Pi Model B. They look like this:

Raspberry Pi Model B

Running Raspbian, upgrade to Debian Jessie, and compile the code:

apt-get install git libusb-1.0-0-dev libncurses-dev gcc g++
git clone --recursive
cd missile

You can then place the pi anywhere with network and power:

USB Missile launcher setup with Raspberry Pi

To operate the launcher remotely, use SSH to log in, and run missile/bin/keyboard-ctl.

Setup 3: Wireless with Raspberry Pi and Battery

Of course, network and power can be provided with a power bank and wifi adapter:

Power Bank for mobile phone
USB WiFi Adapter

The wifi adapter will take some work to set up (see Debian Wiki), so I wont document that here. You will need a power bank that has enough power for the Raspberry Pi with launcher and wifi. Mine had to be close to fully charged to work.

An obligatory diagram of this setup:

USB Missile launcher setup with Raspberry Pi (WiFi and Battery)


The reason this helps with cubicle warfare is simple: The launcher, Raspberry Pi and battery can be fitted into a tissue box or other small space. Proof:

Box interior

On a desk you would see this as:

Box exterior
Box open

And a quick demo for completeness:

In the above, the Pi is connected to DC power, because the battery didn’t have enough juice to power the unit.

Enabling graphical boot on Debian GNU/Linux

Unlike most desktop Linuxes around today, Debian’s default boot screen is still text:

Debian's text-mode booting

I imagine that this is because there is no distinction between “Desktop” and “Server” editions in the Debian world (see tasksel), so a text-mode boot will work on every type of installation.

Luckily, if you want a graphical boot screen, you can simply apt-get install a package called plymouth and configure it according to these instructions.

The result looks more suited to a desktop PC (screen capture from here):

Debian's plymouth boot screen

Plymouth install notes

There is a comment in /etc/default/grub which suggests checking supported graphics modes, which is a Good Idea(TM):

# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'

The theme for “wheezy” was called Joy, so if you have desktop-base installed, you should:

/usr/sbin/plymouth-set-default-theme joy

I tried to get this working in a virtual machine to get an actual screen capture, but on KVM this appears to be quite tricky, due to emulated graphics.