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:

2015-03-printer-back
2015-03-printer-top

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:

dmesg
[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:

<?php
/* 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
exit(0);

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:

<?php
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

Configure asterisk with wildcard extensions

This post covers a common use case when deploying a phone system: Somebody in the business needs to be able to record voicemail greetings for other people.

This assumes that you are already running asterisk, and that people already have something in your Dialplan (extensions.conf) for people to record a greeting:

; Record voicemail greeting
exten => *,1,AGI(scripts/record-voicemail-greeting.pl);

To run this script as somebody else, in the phone sense, we just need to change the CallerID before we run it. Some things you’ll need to know to do this:

  1. An “X” in the extension matches any digit
  2. EXTEN is a variable holding the current extension
  3. CALLERID(num) is another variable, which holds the CallerID number
  4. ${EXTEN:2} is a “substring”, which cuts the first two letters off the extension

With that in mind, if * records your own voicmail, then **4567 would record 4567’s voicemail using this snippet:

; Record other person's voicemail greeting
exten => **XXXX,1,Set(CALLERID(num)=${EXTEN:2})
exten => **XXXX,2,Goto(*,1)

Of course, it would be a terrible idea to enable this for the whole business, which is why you can also check the CallerID before you change it. This alternative snippet allows you to record any voicemail greeting, but only if you are calling from 1234.

; Record other person's voicemail greeting (if calling from phone 1234)
exten => 1234/**XXXX,1,Set(CALLERID(num)=${EXTEN:2})
exten => 1234/**XXXX,2,Goto(*,1)

Two ways to back up your Google Apps account

If you use Gmail or hosted Google Apps, you might be interested in taking a backup of your data, such as emails, Drive documents, and calendar entries. Thankfully, you can usually export copy of your account data using Google Takeout.

If your hosted Apps account has Takeout disabled, then you can do a backup, it simply has a few extra steps.

Option 1: Google Takeout

This method is nice and simple. Simply go to the Data tools – Download your data page, and select which services you want to export:

2015-01-google-takeout

It can be a bit eye-opening to see the amount of data Google has on you (Files, conversations, location history, etc). At this point, click through to “Prepare Download”. Depending on the size of your account, this may take as a coffee break, a few hours, or even an entire day.

2015-02-google-takeout-prepare

If you check the box for it, you’ll get an email like this when your Download completes:

2015-02-google-archive

And this lets you fetch a single file:

2015-01-download

The .zip file contains a series of folders, one for each service. The defaults seem to be:

Mail
A unix mbox file
Calendar
One iCal file for each calendar
Contacts
One vCard file for each group.
Drive
Exports as PDF, docx, xlsx

Option 2: Export data from each service

Sometimes, Google Takeout isn’t an option.

2015-01-google-takeout-disabled

Luckily, most Google services have some sort of data export built in. This means, if you have a new contact manager, or want to include your Drive in your PC backup, it’s still possible.

The export formats in these examples should match the Google Takeout defaults. Tab through each service t see how to export it:

If you are not a power user, then I would suggest setting up a copy of Mozilla Thunderbird via IMAP, and regularly using it for your email. This is a simple way to keep a clone of your inbox on your desktop computer, so that it can be included in backups.

If you are more tech-savvy, then the rest of this section will focus on helping you generate an mbox file containing a full backup of your email, the same format as Takeout uses. The best tool for that is a Linux program called getmail.

On Debian or Ubuntu Linux, issue this command to install getmail:

sudo apt-get install getmail4

For other package managers, see these directions.

First, you need to enable IMAP for your account, see Google’s article: Get started with IMAP and POP3, for the steps.

Now create a file at ~/.getmail/getmailrc, and configure it to read your email account via IMAP/SSL.

[retriever]
type = SimpleIMAPSSLRetriever
server = imap.gmail.com
port = 993
username = bob@mail.example.com
password = ....

[destination]
type = Mboxrd
path = ~/inbox

[options]
verbose = 1
getmail

After some time, you will end up with a large mbox file at ~/inbox, containing all of your mail.

If, for some reason, you need to use POP3 instead, then see this article on Gmail backup

Go to your contacts, and find a group. Check the box next to each name, and then find the Export button:

2015-02-contacts-export

Select the vCard format here, as it’s the same format which Takeout would have used:

2015-02-contacts-export-group-vcard

Google provides a share-able iCal link, which you can download once, but it is only available if your calendar is public.

So, if your calendar isn’t too sensitive, click “Share” and make the calendar public:2015-02-calendar-01-share

2015-02-calendar-02-public

Go to “Calendar Settings”, find the iCal link. It may take a few minutes for the link to start working, but once it does, download it, and then turn off public sharing.

There is a small risk that somebody else loads your calendar while its public, so if this concerns you, then save the events individually.

Exporting from Google Drive is nice and simple. Select all of your files (Shift+Click):

2015-02-drive-select

And then find the Download button:

2015-02-drive-download

If you apply this to your whole drive, it may take a while, so you may wish to download it in parts if your Internet can be unreliable.

Know how to export a different service? Send it in and I’ll add it to the list.

How do I use these files?

Google Drive’s files are exported in familiar formats. If you haven’t used an mbox, vCard or ics file before, then you will need to find a program which can read these for you.

Google’s support answer “Download your data: Per-service information” contains a list of files types which you’ll run into during this process, and suggests programs which can import them.

How to merge edges in GraphViz

If you are sketching out a node in graphviz which has many ancestors, a dense collection of arrow-heads can become unsightly:

Example 1 - Edges not merged

The code for the above graph is:

digraph G {
    {a, b, c} -> d;
    d -> e;
}

To merge the edges together, we can instead point these three nodes to an intermediate node, using edges without arrow. This example is adapted from the GraphViz FAQ (link).

This gives us:

Example 2 - edges merged

digraph G {
    d1 [shape=point,width=0.01,height=0.01];
    {a, b, c} -> d1 [dir=none];
    d1 -> d;
    d -> e;
}

To compile these examples, you can use an online tool such as Webgraphviz. If you have GraphViz installed, then run dot over them:

dot -Tpdf example.dot > example.pdf

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.

Locking down your VOIP setup with a SIP Threat Manager

If you run a Voice over IP network which is available from the Internet, then it’s quite important to lock it down properly, so that it isn’t hijacked for relaying spam calls at your expense.

This article will cover the steps you need to deploy the SIP Threat Manager from Allo, which you can think of as a security-focused SIP proxy & firewall.

Topology

For example, you might have SIP clients both on-site with your server, and also allow people with their own devices to connect directly from home. One of the simplest ways to harden this setup is to add a specialised SIP router between your server and the Internet, to filter connections and log security-relevant events:

VOIP network with STM

VOIP network with STM

This post will show you how to migrate your VOIP network to this more secure topology using Allo’s SIP Threat Manager..

The Allo STM Box

Allo STM front
Allo STM back

The STM itself is only small box, with two 100 Mbit/s Ethernet ports, and two USB ports. It is USB-powered, so one of these ports is for powering the box, and the other is for connecting external storage for log files. It is not power-hungry, and I was able to run it from a laptop USB port without any issues. Other than this, it’s externally a typical embedded network device: it has indicator lights, a power switch, a factory reset button, and a console port.

Allo STM internal

Internally, it runs a MIPS processor, which appears to host Snort on embedded Linux.

Initial Setup

Although the box advertises that it will work out-of-the-box, I found that it was easier to configure the box to match my network, than to re-work my network around the box. This section will simply show you how to get logged in and change the box’s IP address.

First up, I took a look at the console, which is accessible at a baudrate of 38400. It’s far from the most functional CLI around, so I only made use of the factoryreset function to get a clean slate. It showed a few of the open source packages running, such the dropbear sshd, lighttpd and crond among other familiar programs:

Please press Enter to activate this console. 
Starting pid 942, console /dev/ttyS2: '/usr/bin/maincli'
Shield STM Appliance Appliance
shield> factoryreset

Will output 1024 bit rsa secret key to '/etc/dropbear/dropbear_rsa_host_key'
Generating key, this may take a while...
Public key portion is:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgwCKBcVlWK+UiiELbg2CNfOt9rNmj51dmyz7d10MgRfAk9XU9x+kmlMueCFEBMTchsaywigLw0yFqeMZ
Fingerprint: md5 50:5b:c2:64:d4:87:f8:86:ab:c6:e1:59:e4:16:c2:cf
Generating a 1024 bit RSA private key
...++++++
...................++++++
writing new private key to '/etc/lighttpd/webserver.pem'
-----
ip: RTNETLINK answers: No such process
Jan  1 00:40:41 crond[875]: crond 2.3.2 dillon, started, log level 8

mount: mounting /dev/sda1 on /cf/disk failed

The CLI command show ip confirmed that the default IP of the box is 192.168.100.1, netmask 255.255.255.0. From a Linux laptop, you can change IP to something nearby and confirm that you can see the device with these commands:

# ifconfig wlan0 192.168.100.2 netmask 255.255.255.0
# arp-scan -l
Interface: wlan0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.8.1 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
192.168.100.1	00:17:f7:00:9b:0a	CEM Solutions Pvt Ltd

The STM is then accessible via the web address https://192.168.100.1, with the default credentials admin / admin.

STM login

The IP setup is located under Device → General Setup. Change this to DHCP or a spare address on your network:

STM IP config

Configuration

Now that you can access the STM from any device, your first task is to change the admin password. The button for this is in the top-right:

2015-01-stm-screen21

The STM only allows one session at a time- whilst it’s a good idea not to log in twice, this was a surprising limitation. At the STM does not act as its own SIP endpoint, my server was already able to contact the Internet through it at this point.

I quickly screen captured the available settings so that you can click through them. Some of these are SIP-specific, and others of which are general firewall features. One of the more interesting features which you can’t set up with iptables is location-based IP filtering. This could, for example, block problematic SIP calls coming from fraud hotspots in areas where your organisation doesn’t operate.

So once the network is set up on the STM, no changes need to be applied to your SIP server, other than its gateway or IP.

Notes

Whilst this box works as it’s supposed to, I found it to have an un-polished user experience.

  • The network interface labels on the box had a label over them with the opposite information.
  • The box is closed on port 80: It doesn’t reply to HTTP requests, even to redirect them to HTTPS.
  • The command prompt wasn’t as useful as other network devices.
  • I couldn’t get SSH login or NTP to work, although I didn’t investigate these in great detail.
  • The LAN interface (but not the WAN interface) did not light up when connected to a Gigabit POE network, but did work on a 100 megabit network.

However, there are some positives: The 100 Mbit/s interfaces are more than sufficient for voice traffic, the configuration was simple, and USB is a good choice of power supply for equipment which can be connected directly to a server.

Do you really need another box?

This depends on your setup. If your VOIP server doesn’t speak to the Internet, then this box wont fit into your topology.

If it only sees the outside world via an ISP-run SIP trunk, then this type of security is probably not necessary either. Security measures you would use instead are:

  • Use firewall rules to restrict connections so that only the SIP trunk can speak to your VOIP server.
  • Configure your VOIP server to “stay on the line” for calls (directmedia=no in Asterisk) so that the phones do not speak directly to the trunk, and disallow registration from the Internet.

If your VOIP server accepts connections from the public internet throgh SIP, then some sort of separate, SIP-aware firewall or proxy is highly advisable.

Acknowledgement

Thanks to Allo (allo.com) for sending in the box which is used for this example setup.

How to set up Asterisk in 10 minutes

Asterisk is an open-source IP PABX, meaning it lets you run a phone system over your computer network. Whilst IP telephony has been gaining the upper hand over traditional PABX’s for years, few people outside the industry realise just how easy it is to set up your own phone server.

With this guide, you can turn any Linux device into your own PABX for free. We’ll set up two SIP devices on a small network, so that they can dial each other.

Diagram showing how the two example users will connect to the server

Prepare the environment

First, you should get something with Linux. A virtual machine, a spare laptop, a Raspberry pi- anything.

Install asterisk with one of the following commands, depending on your distribution:

apt-get install asterisk
yum install asterisk

If you don’t have an IP phone handy, then you need a program on your computer which speaks SIP (Session Initiation Protocol). This guide uses Linphone (available for Linux and Windows among other platforms) and the Polycom 331 as examples, but any two SIP endpoints will work just as well for testing.

Make users on the server

Asterisk keeps its configuration in /etc/asterisk. The file we need to edit for this setup is users.conf. Open it up with your favourite text editor:

nano /etc/asterisk/users.conf

The syntax is similar to .ini files. Add two users to the bottom of the file:

[6001]
fullname = Example Bob
secret = 1234
hassip = yes
context = users
host = dynamic

[6002]
fullname = Example Joe
secret = 1234
hassip = yes
context = users
host = dynamic

What does this mean?

[600..]
This is the username, and will become the user’s extension on our small network. Dial 6001 for Bob or 6002 for Joe. Whilst 'bob' and 'joe' could also be used here, numeric usernames are more common.
fullname = ...
Used in the Caller ID.
secret = ...
The password used to log in. In a secure system, you would use something other than 1234!
hassip = yes
This tells Asterisk to make a SIP account for the user. Asterisk supports a few other account types, but SIP is the most widely implemented.
context = users
A context is a bit like a category for the user. The extensions which they can dial depend on this.
host = dynamic
This tells Asterisk that the users don’t have a fixed IP address. This means that they must register periodically with the SIP server so that their IP is known.

To activate these changes, save the file, and reload the configuration through the Asterisk console:

mike# asterisk -r -vvvvvvvvv
CLI> reload
CLI> sip show users
Username                   Secret           Accountcode      Def.Context      ACL  ForcerPort
6002                       1234                              users            No   Yes       
6001                       1234                              users            No   Yes       

All of those v‘s stand for verbose, meaning that the asterisk console will give you more information.

Configure the clients

First you should find out your server’s IP address. From the terminal, you can find this with:

ifconfig

Setting up “Example Joe” on a Linphone instance only takes a few clicks. Add a new account, with 6002 as the identity, and your asterisk server as the proxy address (eg: sip://voip.example.com). Click the image below for an example:

Linphone account setup

Linphone account setup

Meanwhile, the Polycom 331 can be configured as “Example Bob” by navigating the menus on the phone itself, or via the web (suggested). The default login is:

Username:
Polycom
Password:
456
Polycom 331 configuration page

The Polycom 331 web interface.

Your asterisk server address needs to be added under SIP -> Servers -> Server 1, while Example Bob’s identity is added under Lines -> Line1. Click the below images for an example.

Polycom 331 configuration page
Polycom 331 configuration page

Server (SIP) configuration on the left, and line configuration on the right.

Once these are saved, the two clients will register with the server. In SIP, clients periodically register so that the server knows where to find them.

In the asterisk console, you will see something like this:

-- Registered SIP '6001' at 192.168.1.4:5060
   > Saved useragent "PolycomSoundPointIP-SPIP_331-UA/3.3.3.0069" for peer 6001

If registration fails, the console will tell you why, provided that you have set the verbosity high enough

You can check which users have registered with this command:

CLI> sip show peers
Name/username              Host                                    Dyn Forcerport ACL Port     Status     
6001/6001                  192.168.1.4                              D   N             5060     Unmonitored 
6002                       (Unspecified)                            D   N             0        Unmonitored 
2 sip peers [Monitored: 0 online, 0 offline Unmonitored: 1 online, 1 offline]

Unfortunately, even after both users have registered, they aren’t ready to communicate yet.

Add extensions to the server

In the world of VOIP, an extension is not a real loop of copper, but a sequential list of things to do when a number is dialled.

This extra step is where Asterisk gets its flexibility. With your extensions.conf setup, you could set your instance to redirect numbers, or dial for 12 seconds before going to voicemail.

We haven’t told the server what to do, so if “Example Bob” makes a call, it wont work yet:

  == Using SIP RTP CoS mark 5
[Nov 15 07:59:30] NOTICE[6070]: chan_sip.c:22753 handle_request_invite: Call from '6001' (192.168.1.4:5060) to extension '6002' rejected because extension not found in context 'users'.

To add extensions, open extensions.conf up:

nano /etc/asterisk/extensions.conf

The syntax is still INI-like. Under [users], we add the steps for each extension, numbered sequentially. In this case, there is only 1 step for each extension: to dial a SIP user.

[users]
exten => 6001,1,Dial(SIP/6001)
exten => 6002,1,Dial(SIP/6002)

In the Asterisk console, type reload to activate the changes.

Now, as planned, both users on the network can dial each-other and have a chat.

More advanced setups

Ok, time to do a reality check. You’ve built an intercom, but not a full phone system! For a start, you need a way to dial the outside world, and let the outside world dial you. For this, you’ll need to work with hardware and service providers.

New WordPress theme

Since the last major revision of my site setup, I’ve been including more technical content, which would be easier to read with syntax highlighting and tabs.

The most visible part of the transition is now complete:

The old theme was Skittlish, but I decided to move to a new theme which was based on Bootstrap, so that I could use its components. The new theme is a modified version of the default twentyfourteen theme, using the visual style of morphic, with Prism.js added for code highlighting.

From SIP to speaker: Setting up a VOIP Zone Controller

In many scenarios where you find a phone system, you’ll also find a PA system. Ideally, we want the audio from the phones to be able to reach the PA system when a special number is dialled:

Ideal VOIP Zone Controller setup

With analog phone systems, this is a common feature. IP-based systems can do this too, with the help of a VOIP Zone Controller.

I think of a Zone Controller as an “Ethernet to RCA” adapter. This article will show you how to get a CyberData controller set up with Asterisk.

The equipment

I settled on this CyberData 4-port Zone Controller, which is quite small, POE-powered, and is of course a reliable and configurable embedded SIP endpoint:

Front view - CyberData 4 port Zone ControllerBack view - CyberData 4 port Zone Controller

In a typical configuration, the controller would continually receive audio, such as background music, and then silence it when an announcement is made (this is called “night ringer”).

This device has 4 different audio outputs, and waits for a DTMF tone by default, which is then mapped to one or more outputs. I had a very simple use case, and disabled this, so that dialing the box simply caused audio output.

On the VOIP server

Hopefully, you’re using Asterisk to run your phone network! This is the usual platform for VOIP enthusiasts. If you’re dealing with a proprietary system, then you’ll need to skip this section.

First, sip.conf needs to have an entry for the zone controller. This should look like a regular phone. Without going into the intricacies of Asterisk’s SIP configuration, this snippet adds the zone controller as user 1234. It’s given the caller ID “PA System”, and is allowed it to connect from any IP address with the specified secret:

[1234]
type = peer
host = dynamic
context = users
hassip = yes
directmedia = no
fullname = PA System
callerid = PA System
secret = ... (something random here) ...
nat = no

In the extensions.conf, you can then make the device contactable by all phones by adding a line to the users context:

[users]
exten => 1234,1,Dial(SIP/1234)

Setting up the controller

First, you need to plug the audio out into some sort of speaker, and the Ethernet into a POE network with an IP phone system.

In operation - CyberData 4 port Zone Controller

The CyberData devices have a web interface, so you need to find it on the network. I suggest filtering the output of arp-scan -l eth0 on GNU/Linux, which will find the IP address corresponding to the MAC address printed on your device.

Once you’ve found it, the configuration is dead simple. Type the IP address into a web browser, and log in as user “admin”, password “admin”.

Main configuration- CyberData 4 port Zone Controller

The main configuration page of the zone controller.

The web interface is extensive, and shows the depth of options which are used in this niche application, such as custom audio snippets and test routines.

Of course, you may want to adjust the network configuration, which is on the “Network Configuration” page. Once you adjust anything, no changes will take effect until you save and reboot.

IP configuration- CyberData 4 port Zone Controller

IP configuration panel

Remembering the login and password you set up in sip.conf, you will need to fill in the SIP configuration as well.

SIP configuration- CyberData 4 port Zone Controller

SIP configuration panel

Once you reboot, you should see the device register from the Asterisk console, and it will then be reachable. More configuration options to explore include:

  • Tick “Bypass SIP DTMF Entry” in “Zone Config” if you don’t have zones.
  • Set the admin password (!)
  • Take a backup by exporting the configuration from the main page

Good luck!

What is ESC/POS, and how do I use it?

ESC/POS is the command set which makes receipt printers print-

Introduction

Before we begin, there’s three things you need to know about ESC/POS:

  1. Most modern receipt printers support it in some form.
  2. It’s dead simple to write.
  3. Commands start with an ESC character (ASCII 27).

The most useful reference for the protocol is this Epson FAQ, which I’ve used previously to implement an ESC/POS printer driver for PHP.

Download printing library (PHP code)

Incidentally, the receipt printed in the above video is an example from the escpos-php repository. I’ll step through this print-out, as it demonstrates all of the basic ESC/POS features.

Command structure

Four specific ASCII characters make appearances in the command sequences-

Abbreviation Name Code (Hex)
NUL Null 0x00
LF Line Feed 0x0A
ESC Escape 0x1B
GS Group Separator 0x1D

Regular text is simply sent to the printer, separated by line-breaks. Commands begin with ESC or GS, and are followed by a printable character, and sometimes some numbers

Numbers are simply passed as a character. For example, ‘5’ is passed as 0x05.

Examples

These examples are taken from the output of demo.php.

Initialisation

When you first connect to the printer, you should initialise it. This reverts to default formatting, rather than the triple-underlined double-strike font which the previous print-out may have been using.

The command to reset the formatting is:

ESC @
require __DIR__ . '/autoload.php';
use Mike42\Escpos\Printer;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
$connector = new FilePrintConnector("/dev/usb/lp0");
$printer = new Printer($connector);
$printer -> close();
00000000  1b 40                                             |.@|
00000003

“Hello world” text

This is the simplest type of receipt, and contains only unformatted text.

'Hello world' receipt example
Text is simply sent to the printer, separated by line-feeds.
require __DIR__ . '/autoload.php';
use Mike42\Escpos\Printer;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
$connector = new FilePrintConnector("/dev/usb/lp0");
$printer = new Printer($connector);

/* Text */
$printer -> text("Hello world\n");
$printer -> cut();
$printer -> close();
00000000  1b 40 48 65 6c 6c 6f 20  77 6f 72 6c 64 0a 1d 56  |.@Hello world..V|
00000010  41 03                                             |A.|
00000012

Line feeds

The printer can quickly skip past a given number of lines with this command.

Line feed receipt example
The commands are:

LF
ESC d [ number ]
ESC v [ number ]

The first command feeds forward, the second feeds in reverse. From the example, it can be seen that the demo printer does not support reverse paper feeding.

require_once(dirname(__FILE__) . "/escpos-php/Escpos.php");
$printer = new Escpos();

/* Line feeds */
$printer -> text("ABC");
$printer -> feed(7);
$printer -> text("DEF");
$printer -> feedReverse(3);
$printer -> text("GHI");
$printer -> feed();
$printer -> cut();
$printer -> close();
00000000  1b 40 41 42 43 1b 64 07  44 45 46 1b 65 03 47 48  |.@ABC.d.DEF.e.GH|
00000010  49 0a 1d 56 41 03                                 |I..VA.|
00000016

Print modes

Print modes include font height, width and boldness into a single attribute.

Font mode receipt example
The command is:

ESC ! [number]

The font modes are made from logically OR’ing together a selection of attributes. 0 represents plan Font A text. Mode flags are:

Mode Number
Font A (no mode) 0
Font B 1
Emphasized 8
Double height 16
Double width 32
Underline 128

The example receipt illustrates the effect of each flag.

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

/* Font modes */
$modes = array(
    Printer:::MODE_FONT_A,
    Printer:::MODE_FONT_B,
    Printer:::MODE_EMPHASIZED,
    Printer:::MODE_DOUBLE_HEIGHT,
    Printer:::MODE_DOUBLE_WIDTH,
    Printer:::MODE_UNDERLINE);
for($i = 0; $i < 2 ** count($modes); $i++) {
    $bits = str_pad(decbin($i), count($modes), "0", STR_PAD_LEFT);
    $mode = 0;
    for($j = 0; $j < strlen($bits); $j++) {
        if(substr($bits, $j, 1) == "1") {
            $mode |= $modes[$j];
        }
    }
    $printer -> selectPrintMode($mode);
    $printer -> text("ABCDEFGHIJabcdefghijk\n");
}
$printer -> selectPrintMode(); // Reset
$printer -> cut();
$printer -> close();
00000000  1b 40 1b 21 00 41 42 43  44 45 46 47 48 49 4a 61  |.@.!.ABCDEFGHIJa|
00000010  62 63 64 65 66 67 68 69  6a 6b 0a 1b 21 80 41 42  |bcdefghijk..!.AB|
00000020  43 44 45 46 47 48 49 4a  61 62 63 64 65 66 67 68  |CDEFGHIJabcdefgh|
00000030  69 6a 6b 0a 1b 21 20 41  42 43 44 45 46 47 48 49  |ijk..! ABCDEFGHI|
00000040  4a 61 62 63 64 65 66 67  68 69 6a 6b 0a 1b 21 a0  |Jabcdefghijk..!.|
00000050  41 42 43 44 45 46 47 48  49 4a 61 62 63 64 65 66  |ABCDEFGHIJabcdef|
00000060  67 68 69 6a 6b 0a 1b 21  10 41 42 43 44 45 46 47  |ghijk..!.ABCDEFG|
00000070  48 49 4a 61 62 63 64 65  66 67 68 69 6a 6b 0a 1b  |HIJabcdefghijk..|
00000080  21 90 41 42 43 44 45 46  47 48 49 4a 61 62 63 64  |!.ABCDEFGHIJabcd|
00000090  65 66 67 68 69 6a 6b 0a  1b 21 30 41 42 43 44 45  |efghijk..!0ABCDE|
000000a0  46 47 48 49 4a 61 62 63  64 65 66 67 68 69 6a 6b  |FGHIJabcdefghijk|
000000b0  0a 1b 21 b0 41 42 43 44  45 46 47 48 49 4a 61 62  |..!.ABCDEFGHIJab|
000000c0  63 64 65 66 67 68 69 6a  6b 0a 1b 21 08 41 42 43  |cdefghijk..!.ABC|
000000d0  44 45 46 47 48 49 4a 61  62 63 64 65 66 67 68 69  |DEFGHIJabcdefghi|
000000e0  6a 6b 0a 1b 21 88 41 42  43 44 45 46 47 48 49 4a  |jk..!.ABCDEFGHIJ|
000000f0  61 62 63 64 65 66 67 68  69 6a 6b 0a 1b 21 28 41  |abcdefghijk..!(A|
00000100  42 43 44 45 46 47 48 49  4a 61 62 63 64 65 66 67  |BCDEFGHIJabcdefg|
00000110  68 69 6a 6b 0a 1b 21 a8  41 42 43 44 45 46 47 48  |hijk..!.ABCDEFGH|
00000120  49 4a 61 62 63 64 65 66  67 68 69 6a 6b 0a 1b 21  |IJabcdefghijk..!|
00000130  18 41 42 43 44 45 46 47  48 49 4a 61 62 63 64 65  |.ABCDEFGHIJabcde|
00000140  66 67 68 69 6a 6b 0a 1b  21 98 41 42 43 44 45 46  |fghijk..!.ABCDEF|
00000150  47 48 49 4a 61 62 63 64  65 66 67 68 69 6a 6b 0a  |GHIJabcdefghijk.|
00000160  1b 21 38 41 42 43 44 45  46 47 48 49 4a 61 62 63  |.!8ABCDEFGHIJabc|
00000170  64 65 66 67 68 69 6a 6b  0a 1b 21 b8 41 42 43 44  |defghijk..!.ABCD|
00000180  45 46 47 48 49 4a 61 62  63 64 65 66 67 68 69 6a  |EFGHIJabcdefghij|
00000190  6b 0a 1b 21 01 41 42 43  44 45 46 47 48 49 4a 61  |k..!.ABCDEFGHIJa|
000001a0  62 63 64 65 66 67 68 69  6a 6b 0a 1b 21 81 41 42  |bcdefghijk..!.AB|
000001b0  43 44 45 46 47 48 49 4a  61 62 63 64 65 66 67 68  |CDEFGHIJabcdefgh|
000001c0  69 6a 6b 0a 1b 21 21 41  42 43 44 45 46 47 48 49  |ijk..!!ABCDEFGHI|
000001d0  4a 61 62 63 64 65 66 67  68 69 6a 6b 0a 1b 21 a1  |Jabcdefghijk..!.|
000001e0  41 42 43 44 45 46 47 48  49 4a 61 62 63 64 65 66  |ABCDEFGHIJabcdef|
000001f0  67 68 69 6a 6b 0a 1b 21  11 41 42 43 44 45 46 47  |ghijk..!.ABCDEFG|
00000200  48 49 4a 61 62 63 64 65  66 67 68 69 6a 6b 0a 1b  |HIJabcdefghijk..|
00000210  21 91 41 42 43 44 45 46  47 48 49 4a 61 62 63 64  |!.ABCDEFGHIJabcd|
00000220  65 66 67 68 69 6a 6b 0a  1b 21 31 41 42 43 44 45  |efghijk..!1ABCDE|
00000230  46 47 48 49 4a 61 62 63  64 65 66 67 68 69 6a 6b  |FGHIJabcdefghijk|
00000240  0a 1b 21 b1 41 42 43 44  45 46 47 48 49 4a 61 62  |..!.ABCDEFGHIJab|
00000250  63 64 65 66 67 68 69 6a  6b 0a 1b 21 09 41 42 43  |cdefghijk..!.ABC|
00000260  44 45 46 47 48 49 4a 61  62 63 64 65 66 67 68 69  |DEFGHIJabcdefghi|
00000270  6a 6b 0a 1b 21 89 41 42  43 44 45 46 47 48 49 4a  |jk..!.ABCDEFGHIJ|
00000280  61 62 63 64 65 66 67 68  69 6a 6b 0a 1b 21 29 41  |abcdefghijk..!)A|
00000290  42 43 44 45 46 47 48 49  4a 61 62 63 64 65 66 67  |BCDEFGHIJabcdefg|
000002a0  68 69 6a 6b 0a 1b 21 a9  41 42 43 44 45 46 47 48  |hijk..!.ABCDEFGH|
000002b0  49 4a 61 62 63 64 65 66  67 68 69 6a 6b 0a 1b 21  |IJabcdefghijk..!|
000002c0  19 41 42 43 44 45 46 47  48 49 4a 61 62 63 64 65  |.ABCDEFGHIJabcde|
000002d0  66 67 68 69 6a 6b 0a 1b  21 99 41 42 43 44 45 46  |fghijk..!.ABCDEF|
000002e0  47 48 49 4a 61 62 63 64  65 66 67 68 69 6a 6b 0a  |GHIJabcdefghijk.|
000002f0  1b 21 39 41 42 43 44 45  46 47 48 49 4a 61 62 63  |.!9ABCDEFGHIJabc|
00000300  64 65 66 67 68 69 6a 6b  0a 1b 21 b9 41 42 43 44  |defghijk..!.ABCD|
00000310  45 46 47 48 49 4a 61 62  63 64 65 66 67 68 69 6a  |EFGHIJabcdefghij|
00000320  6b 0a 1b 21 00 41 42 43  44 45 46 47 48 49 4a 61  |k..!.ABCDEFGHIJa|
00000330  62 63 64 65 66 67 68 69  6a 6b 0a 1b 21 80 41 42  |bcdefghijk..!.AB|
00000340  43 44 45 46 47 48 49 4a  61 62 63 64 65 66 67 68  |CDEFGHIJabcdefgh|
00000350  69 6a 6b 0a 1b 21 20 41  42 43 44 45 46 47 48 49  |ijk..! ABCDEFGHI|
00000360  4a 61 62 63 64 65 66 67  68 69 6a 6b 0a 1b 21 a0  |Jabcdefghijk..!.|
00000370  41 42 43 44 45 46 47 48  49 4a 61 62 63 64 65 66  |ABCDEFGHIJabcdef|
00000380  67 68 69 6a 6b 0a 1b 21  10 41 42 43 44 45 46 47  |ghijk..!.ABCDEFG|
00000390  48 49 4a 61 62 63 64 65  66 67 68 69 6a 6b 0a 1b  |HIJabcdefghijk..|
000003a0  21 90 41 42 43 44 45 46  47 48 49 4a 61 62 63 64  |!.ABCDEFGHIJabcd|
000003b0  65 66 67 68 69 6a 6b 0a  1b 21 30 41 42 43 44 45  |efghijk..!0ABCDE|
000003c0  46 47 48 49 4a 61 62 63  64 65 66 67 68 69 6a 6b  |FGHIJabcdefghijk|
000003d0  0a 1b 21 b0 41 42 43 44  45 46 47 48 49 4a 61 62  |..!.ABCDEFGHIJab|
000003e0  63 64 65 66 67 68 69 6a  6b 0a 1b 21 08 41 42 43  |cdefghijk..!.ABC|
000003f0  44 45 46 47 48 49 4a 61  62 63 64 65 66 67 68 69  |DEFGHIJabcdefghi|
00000400  6a 6b 0a 1b 21 88 41 42  43 44 45 46 47 48 49 4a  |jk..!.ABCDEFGHIJ|
00000410  61 62 63 64 65 66 67 68  69 6a 6b 0a 1b 21 28 41  |abcdefghijk..!(A|
00000420  42 43 44 45 46 47 48 49  4a 61 62 63 64 65 66 67  |BCDEFGHIJabcdefg|
00000430  68 69 6a 6b 0a 1b 21 a8  41 42 43 44 45 46 47 48  |hijk..!.ABCDEFGH|
00000440  49 4a 61 62 63 64 65 66  67 68 69 6a 6b 0a 1b 21  |IJabcdefghijk..!|
00000450  18 41 42 43 44 45 46 47  48 49 4a 61 62 63 64 65  |.ABCDEFGHIJabcde|
00000460  66 67 68 69 6a 6b 0a 1b  21 98 41 42 43 44 45 46  |fghijk..!.ABCDEF|
00000470  47 48 49 4a 61 62 63 64  65 66 67 68 69 6a 6b 0a  |GHIJabcdefghijk.|
00000480  1b 21 38 41 42 43 44 45  46 47 48 49 4a 61 62 63  |.!8ABCDEFGHIJabc|
00000490  64 65 66 67 68 69 6a 6b  0a 1b 21 b8 41 42 43 44  |defghijk..!.ABCD|
000004a0  45 46 47 48 49 4a 61 62  63 64 65 66 67 68 69 6a  |EFGHIJabcdefghij|
000004b0  6b 0a 1b 21 01 41 42 43  44 45 46 47 48 49 4a 61  |k..!.ABCDEFGHIJa|
000004c0  62 63 64 65 66 67 68 69  6a 6b 0a 1b 21 81 41 42  |bcdefghijk..!.AB|
000004d0  43 44 45 46 47 48 49 4a  61 62 63 64 65 66 67 68  |CDEFGHIJabcdefgh|
000004e0  69 6a 6b 0a 1b 21 21 41  42 43 44 45 46 47 48 49  |ijk..!!ABCDEFGHI|
000004f0  4a 61 62 63 64 65 66 67  68 69 6a 6b 0a 1b 21 a1  |Jabcdefghijk..!.|
00000500  41 42 43 44 45 46 47 48  49 4a 61 62 63 64 65 66  |ABCDEFGHIJabcdef|
00000510  67 68 69 6a 6b 0a 1b 21  11 41 42 43 44 45 46 47  |ghijk..!.ABCDEFG|
00000520  48 49 4a 61 62 63 64 65  66 67 68 69 6a 6b 0a 1b  |HIJabcdefghijk..|
00000530  21 91 41 42 43 44 45 46  47 48 49 4a 61 62 63 64  |!.ABCDEFGHIJabcd|
00000540  65 66 67 68 69 6a 6b 0a  1b 21 31 41 42 43 44 45  |efghijk..!1ABCDE|
00000550  46 47 48 49 4a 61 62 63  64 65 66 67 68 69 6a 6b  |FGHIJabcdefghijk|
00000560  0a 1b 21 b1 41 42 43 44  45 46 47 48 49 4a 61 62  |..!.ABCDEFGHIJab|
00000570  63 64 65 66 67 68 69 6a  6b 0a 1b 21 09 41 42 43  |cdefghijk..!.ABC|
00000580  44 45 46 47 48 49 4a 61  62 63 64 65 66 67 68 69  |DEFGHIJabcdefghi|
00000590  6a 6b 0a 1b 21 89 41 42  43 44 45 46 47 48 49 4a  |jk..!.ABCDEFGHIJ|
000005a0  61 62 63 64 65 66 67 68  69 6a 6b 0a 1b 21 29 41  |abcdefghijk..!)A|
000005b0  42 43 44 45 46 47 48 49  4a 61 62 63 64 65 66 67  |BCDEFGHIJabcdefg|
000005c0  68 69 6a 6b 0a 1b 21 a9  41 42 43 44 45 46 47 48  |hijk..!.ABCDEFGH|
000005d0  49 4a 61 62 63 64 65 66  67 68 69 6a 6b 0a 1b 21  |IJabcdefghijk..!|
000005e0  19 41 42 43 44 45 46 47  48 49 4a 61 62 63 64 65  |.ABCDEFGHIJabcde|
000005f0  66 67 68 69 6a 6b 0a 1b  21 99 41 42 43 44 45 46  |fghijk..!.ABCDEF|
00000600  47 48 49 4a 61 62 63 64  65 66 67 68 69 6a 6b 0a  |GHIJabcdefghijk.|
00000610  1b 21 39 41 42 43 44 45  46 47 48 49 4a 61 62 63  |.!9ABCDEFGHIJabc|
00000620  64 65 66 67 68 69 6a 6b  0a 1b 21 b9 41 42 43 44  |defghijk..!.ABCD|
00000630  45 46 47 48 49 4a 61 62  63 64 65 66 67 68 69 6a  |EFGHIJabcdefghij|
00000640  6b 0a 1b 21 00 1d 56 41  03                       |k..!..VA.|
00000649

Underline

Underline receipt example
The command is:

ESC – [ number ]

The argument is set to 0 for no underline, 1 for underline, 2 for heavy underline.

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

/* Underline */
for($i = 0; $i < 3; $i++) {
    $printer -> setUnderline($i);
    $printer -> text("The quick brown fox jumps over the lazy dog\n");
}
$printer -> setUnderline(0); // Reset
$printer -> cut();
$printer -> close();
00000000  1b 40 1b 2d 00 54 68 65  20 71 75 69 63 6b 20 62  |.@.-.The quick b|
00000010  72 6f 77 6e 20 66 6f 78  20 6a 75 6d 70 73 20 6f  |rown fox jumps o|
00000020  76 65 72 20 74 68 65 20  6c 61 7a 79 20 64 6f 67  |ver the lazy dog|
00000030  0a 1b 2d 01 54 68 65 20  71 75 69 63 6b 20 62 72  |..-.The quick br|
00000040  6f 77 6e 20 66 6f 78 20  6a 75 6d 70 73 20 6f 76  |own fox jumps ov|
00000050  65 72 20 74 68 65 20 6c  61 7a 79 20 64 6f 67 0a  |er the lazy dog.|
00000060  1b 2d 02 54 68 65 20 71  75 69 63 6b 20 62 72 6f  |.-.The quick bro|
00000070  77 6e 20 66 6f 78 20 6a  75 6d 70 73 20 6f 76 65  |wn fox jumps ove|
00000080  72 20 74 68 65 20 6c 61  7a 79 20 64 6f 67 0a 1b  |r the lazy dog..|
00000090  2d 00 1d 56 41 03                                 |-..VA.|
00000096

Cuts

The command is:

ESC V [ number ]

The argument apparently represents whether to perform a ‘partial’ (65) or ‘full’ (66) cut, but has no effect on my model of printer.

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

/* Cuts */
for($i = 0; $i < 5; $i++) {
    $printer -> cut(Printer:::CUT_PARTIAL);
    $printer -> cut(Printer:::CUT_FULL);
}
$printer -> cut();
$printer -> close();
00000000  1b 40 1d 56 42 03 1d 56  41 03 1d 56 42 03 1d 56  |.@.VB..VA..VB..V|
00000010  41 03 1d 56 42 03 1d 56  41 03 1d 56 42 03 1d 56  |A..VB..VA..VB..V|
00000020  41 03 1d 56 42 03 1d 56  41 03 1d 56 41 03        |A..VB..VA..VA.|
0000002e

Emphasis

Receipt emphasis example
The command is:

ESC E [ number ]

Use 1 to enable emphasis, and 0 to disable it.

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

/* Emphasis */
for($i = 0; $i < 2; $i++) {
    $printer -> setEmphasis($i == 1);
    $printer -> text("The quick brown fox jumps over the lazy dog\n");
}
$printer -> setEmphasis(false); // Reset
$printer -> cut();
$printer -> close();
00000000  1b 40 1b 45 00 54 68 65  20 71 75 69 63 6b 20 62  |.@.E.The quick b|
00000010  72 6f 77 6e 20 66 6f 78  20 6a 75 6d 70 73 20 6f  |rown fox jumps o|
00000020  76 65 72 20 74 68 65 20  6c 61 7a 79 20 64 6f 67  |ver the lazy dog|
00000030  0a 1b 45 01 54 68 65 20  71 75 69 63 6b 20 62 72  |..E.The quick br|
00000040  6f 77 6e 20 66 6f 78 20  6a 75 6d 70 73 20 6f 76  |own fox jumps ov|
00000050  65 72 20 74 68 65 20 6c  61 7a 79 20 64 6f 67 0a  |er the lazy dog.|
00000060  1b 45 00 1d 56 41 03                              |.E..VA.|
00000067

Double-strike

Double-strike receipt example
The command is:

ESC G [ number ]

Use 1 to enable, or 0 to disable. On the model tested here, this appears to be identical to the “emphasis” mode above.

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

/* Double-strike (looks basically the same as emphasis) */
for($i = 0; $i < 2; $i++) {
    $printer -> setDoubleStrike($i == 1);
    $printer -> text("The quick brown fox jumps over the lazy dog\n");
}
$printer -> setDoubleStrike(false);
$printer -> cut();
$printer -> close();
00000000  1b 40 1b 47 00 54 68 65  20 71 75 69 63 6b 20 62  |.@.G.The quick b|
00000010  72 6f 77 6e 20 66 6f 78  20 6a 75 6d 70 73 20 6f  |rown fox jumps o|
00000020  76 65 72 20 74 68 65 20  6c 61 7a 79 20 64 6f 67  |ver the lazy dog|
00000030  0a 1b 47 01 54 68 65 20  71 75 69 63 6b 20 62 72  |..G.The quick br|
00000040  6f 77 6e 20 66 6f 78 20  6a 75 6d 70 73 20 6f 76  |own fox jumps ov|
00000050  65 72 20 74 68 65 20 6c  61 7a 79 20 64 6f 67 0a  |er the lazy dog.|
00000060  1b 47 00 1d 56 41 03                              |.G..VA.|
00000067

Fonts

Receipt fonts example
The command is:

ESC M [ number ]

There are three possible fonts, documented as “A”, “B” and “C”, and numbered 0, 1, and 2. Many printers, including this one, don’t have Font C.

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

/* Fonts (many printers do not have a 'Font C') */
$fonts = array(
    Printer:::FONT_A,
    Printer:::FONT_B,
    Printer:::FONT_C);
for($i = 0; $i < count($fonts); $i++) {
    $printer -> setFont($fonts[$i]);
    $printer -> text("The quick brown fox jumps over the lazy dog\n");
}
$printer -> setFont(); // Reset
$printer -> cut();
$printer -> close();
00000000  1b 40 1b 4d 00 54 68 65  20 71 75 69 63 6b 20 62  |.@.M.The quick b|
00000010  72 6f 77 6e 20 66 6f 78  20 6a 75 6d 70 73 20 6f  |rown fox jumps o|
00000020  76 65 72 20 74 68 65 20  6c 61 7a 79 20 64 6f 67  |ver the lazy dog|
00000030  0a 1b 4d 01 54 68 65 20  71 75 69 63 6b 20 62 72  |..M.The quick br|
00000040  6f 77 6e 20 66 6f 78 20  6a 75 6d 70 73 20 6f 76  |own fox jumps ov|
00000050  65 72 20 74 68 65 20 6c  61 7a 79 20 64 6f 67 0a  |er the lazy dog.|
00000060  1b 4d 02 54 68 65 20 71  75 69 63 6b 20 62 72 6f  |.M.The quick bro|
00000070  77 6e 20 66 6f 78 20 6a  75 6d 70 73 20 6f 76 65  |wn fox jumps ove|
00000080  72 20 74 68 65 20 6c 61  7a 79 20 64 6f 67 0a 1b  |r the lazy dog..|
00000090  4d 00 1d 56 41 03                                 |M..VA.|
00000096

Justification

Receipt justification example
The command is:

ESC a [ number ]

Use 0 to justify left, 1 to centre the text, or 2 to right-align it.

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

/* Justification */
$justification = array(
    Printer:::JUSTIFY_LEFT,
    Printer:::JUSTIFY_CENTER,
    Printer:::JUSTIFY_RIGHT);
for($i = 0; $i < count($justification); $i++) {
    $printer -> setJustification($justification[$i]);
    $printer -> text("A man a plan a canal panama\n");
}
$printer -> setJustification(); // Reset
$printer -> cut();
$printer -> close();
00000000  1b 40 1b 61 00 41 20 6d  61 6e 20 61 20 70 6c 61  |.@.a.A man a pla|
00000010  6e 20 61 20 63 61 6e 61  6c 20 70 61 6e 61 6d 61  |n a canal panama|
00000020  0a 1b 61 01 41 20 6d 61  6e 20 61 20 70 6c 61 6e  |..a.A man a plan|
00000030  20 61 20 63 61 6e 61 6c  20 70 61 6e 61 6d 61 0a  | a canal panama.|
00000040  1b 61 02 41 20 6d 61 6e  20 61 20 70 6c 61 6e 20  |.a.A man a plan |
00000050  61 20 63 61 6e 61 6c 20  70 61 6e 61 6d 61 0a 1b  |a canal panama..|
00000060  61 00 1d 56 41 03                                 |a..VA.|
00000066

Barcodes

Barcoded receipt example
The commands are:

GS h [ number ]
ESC k [ number ] [ text ] NUL

The first command sets the barcode height — measured in dots, while the second one prints the actual barcode. The number represents the barcode standard, which for most purposes should be “4”, representing CODE39. 6 standards are supported by the PHP driver.

You will notice that due to driver glitches or printer incompatibility, not all of the barcodes print! As above, my advice is to use CODE39 if you run into this.

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

/* Barcodes */
$barcodes = array(
    Printer:::BARCODE_UPCA,
    Printer:::BARCODE_UPCE,
    Printer:::BARCODE_JAN13,
    Printer:::BARCODE_JAN8,
    Printer:::BARCODE_CODE39,
    Printer:::BARCODE_ITF,
    Printer:::BARCODE_CODABAR);
$printer -> setBarcodeHeight(80);
for($i = 0; $i < count($barcodes); $i++) {
    $printer -> text("Barcode $i " . "\n");
    $printer -> barcode("9876", $barcodes[$i]);
    $printer -> feed();
}
$printer -> cut();
$printer -> close();
00000000  1b 40 1d 68 50 42 61 72  63 6f 64 65 20 30 20 0a  |.@.hPBarcode 0 .|
00000010  1d 6b 00 39 38 37 36 00  0a 42 61 72 63 6f 64 65  |.k.9876..Barcode|
00000020  20 31 20 0a 1d 6b 01 39  38 37 36 00 0a 42 61 72  | 1 ..k.9876..Bar|
00000030  63 6f 64 65 20 32 20 0a  1d 6b 02 39 38 37 36 00  |code 2 ..k.9876.|
00000040  0a 42 61 72 63 6f 64 65  20 33 20 0a 1d 6b 03 39  |.Barcode 3 ..k.9|
00000050  38 37 36 00 0a 42 61 72  63 6f 64 65 20 34 20 0a  |876..Barcode 4 .|
00000060  1d 6b 04 39 38 37 36 00  0a 42 61 72 63 6f 64 65  |.k.9876..Barcode|
00000070  20 35 20 0a 1d 6b 05 39  38 37 36 00 0a 42 61 72  | 5 ..k.9876..Bar|
00000080  63 6f 64 65 20 36 20 0a  1d 6b 06 39 38 37 36 00  |code 6 ..k.9876.|
00000090  0a 1d 56 41 03                                    |..VA.|
00000095

Resources

Linked previously in this post:

And if you’ve just received an Epson printer and need to figure out how it works:

Update 2015-03-10: Re-wrote examples for the newer version of escpos-php.

Update 2016-04-22: Updated examples again to match the latest driver code.