I recently combined a few tools on Linux to create a local Websocket listener, which could forward raw data to a USB printer, so that it could be accessed using Javascript in a web browser.
Why would you want this? I have point of sale applications (POS) in mind, which need to send raw data to a printer. For these applications, the browser and operating system print systems are not appropriate, since they prompt, spool, and badly render pages by converting them to low-fidelity raster images.
Web interfaces are becoming common for point-of-sale applications. The web page could be served from somewhere outside your local network, which is why we need to get the client-side Javascript involved.
The tools
To run on the client computer:
And to generate the print data on the webserver:
We will use these tools to provide some plumbing, so that we can retrieve the print data, and send it off to the printer from client-side Javascript.
Client computer
The client computer was a Linux desktop system. Both of the tools we need are available in the Debian repositories:
sudo apt-get install websockify socat
Listen for websocket connections on port 5555
and pass them to localhost:7000
:
websockify 5555 localhost:7000
Listen for TCP connections on localhost port 7000 and pass them to the USB device (more advanced version of this previous post):
socat -u TCP-LISTEN:7000,fork,reuseaddr,bind=127.0.0.1 OPEN:/dev/usb/lp0
Web page
I made a self-contained web-page to provide a button which requested a print file from the network and passed it to the local websocket.
This is slightly modified from a similar example that I used for a previous project.
<html>
<head>
<meta charset="UTF-8">
<title>Web-based raw printing example</title>
</head>
<body>
<h1>Web-based raw printing example</h1>
<p>This snippet forwards raw data to a local websocket.</p>
<form>
<input type="button" onclick="directPrintBytes(printSocket, [0x1b, 0x40, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a, 0x1d, 0x56, 0x41, 0x03]);" value="Print test string"/>
<input type="button" onclick="directPrintFile(printSocket, 'receipt-with-logo.bin');" value="Load and print 'receipt-with-logo'" />
</form>
<script type="text/javascript">
/**
* Retrieve binary data via XMLHttpRequest and print it.
*/
function directPrintFile(socket, path) {
// Get binary data
var req = new XMLHttpRequest();
req.open("GET", path, true);
req.responseType = "arraybuffer";
console.log("directPrintFile(): Making request for binary file");
req.onload = function (oEvent) {
console.log("directPrintFile(): Response received");
var arrayBuffer = req.response; // Note: not req.responseText
if (arrayBuffer) {
var result = directPrint(socket, arrayBuffer);
if(!result) {
alert('Failed, check the console for more info.');
}
}
};
req.send(null);
}
/**
* Extract binary data from a byte array print it.
*/
function directPrintBytes(socket, bytes) {
var result = directPrint(socket, new Uint8Array(bytes).buffer);
if(!result) {
alert('Failed, check the console for more info.');
}
}
/**
* Send ArrayBuffer of binary data.
*/
function directPrint(socket, printData) {
// Type check
if (!(printData instanceof ArrayBuffer)) {
console.log("directPrint(): Argument type must be ArrayBuffer.")
return false;
}
if(printSocket.readyState !== printSocket.OPEN) {
console.log("directPrint(): Socket is not open!");
return false;
}
// Serialise, send.
console.log("Sending " + printData.byteLength + " bytes of print data.");
printSocket.send(printData);
return true;
}
/**
* Connect to print server on startup.
*/
var printSocket = new WebSocket("ws://localhost:5555", ["binary"]);
printSocket.binaryType = 'arraybuffer';
printSocket.onopen = function (event) {
console.log("Socket is connected.");
}
printSocket.onerror = function(event) {
console.log('Socket error', event);
};
printSocket.onclose = function(event) {
console.log('Socket is closed');
}
</script>
</body>
</html>
Webserver
On a Apache HTTP webserver, I uploaded the above webpage, and a file with some raw print data, called receipt-with-logo.bin
. This file was generated with escpos-php and is available in the repository:
For reference, the test file receipt-with-logo.bin
contains this content:
Test
I opened up the web page on the client computer with socat
, websockify
and an Epson TM-T20II connected. After clicking the “Print” button, the file was sent to my printer. Success!
Because I wasn’t closing the websocket
connection, only one browser window could access the printer at a time. Still, it’s a good demo of the basic idea.
To take this from an example to something you might deploy, you would basically just need to keep socat and websockify running in the background as a service (via systemd), close the socket when it’s not being used, and integrate it into a real app.
Different printers, different forwarding
The socat
tool can connect to USB, Serial, or Ethernet printers fairly easily.
USB
Forward TCP connections from port 7000 to the receipt printer at /dev/usb/lp0
:
socat TCP4-LISTEN:7000,fork /dev/usb/lp0
You can also access the device files directly under /sys/bus/usb/devices/
Serial
Forward TCP connections from port 7000 to the receipt printer at /dev/usb/ttyS0
:
socat TCP4-LISTEN:7000,fork /dev/usb/ttyS0
Network
Forward TCP connections from port 7000 to the receipt printer at 10.1.2.3:9100
:
socat -u TCP-LISTEN:7000,fork,reuseaddr,bind=127.0.0.1 TCP4-CONNECT:10.1.2.3:9100
You can forward websocket connections directly to an Ethernet printer with websockify
:
socat -u TCP-LISTEN:7000,fork,reuseaddr,bind=127.0.0.1 localhost:7000
Other types of printer
If you have another type of printer, such as one accessible only via smbclient
or lpr
, then you will need to write a helper script.
Direct printing is faster, so I don’t use this method. Check the socat EXEC documentation or man socat
if you want to try this.
Future
I’ve had a lot of questions on the escpos-php bug tracker from users who are attempting to print from cloud-hosted apps, which is why I tried this setup.
The browser is a moving target. I have previously written receipt-print-hq/chrome-raw-print, a dedicated app for forwarding WebSocket connections to USB, but that will stop working in a few months when Chrome apps are discontinued. Some time later, WebUSB should become available to make this type of printer available in the browser, which should be infinitely useful for connecting to accessories in point-of-sale setups.
The available tools for generating ESC/POS (receipt printer) binary from the browser are a long way off reaching feature parity with the likes of escpos-php and python-escpos. If you are looking for a side-project, then this a good choice.
Lastly, the socat -u flag makes this all unidirectional, but many types of devices (not just printers) can respond to commands. I couldn’t the end-to-end path to work without this flag, so don’t expect to be able to read from the printer without doing some extra work.
Useful links
Some links that I found while setting this up-
- Processing Binary Protocols with Client-Side JavaScript
- hagino3000/client.js
- PHP POS Print (Local Server)
Hi Mike.
I’m trying this. It’s possible? Thank you very much for taking the time.
print.php
@Marcelo – There are a few things wrong with that:
$_POST
wont work on the command-line, andOPEN
: is for writing to files, not for running scripts. You can apparently useEXEC:
on the socat command-line and read from standard input in your script, but that’s quite a different setup to the one in this blog post.Best of luck!