Since datakitchen is running in the cloud, of course the printer had to be accessible through the network.
Luckily, we found that POS printers sometimes ship with a build-in ethernet module. One of them is the EPSON TM-20 series, which we used.
Printing vouchers and receipts should be easy, right? Well, it's not easy as one might think.
In this article we share some of the pitfalls we ran into -- so you don't have to.
No library support
It may sound obvious, but you absolutely want to use a printer that is supported by some library you found that you can use from within your programming language of choice. And if you don't find any, we advise that you should seriously consider to use a language that offers a good one. Maybe write a small REST service in that other programming language and use that from your main program.
If you don't, obviously you have to implement a library yourself. This at least includes implementing the POS protocol used by most of these printers.
You'd also want to support unicode (or utf-8) and possibly want to print images (which have to be encoded in a special format), and maybe also print bar and QR codes. If you still want to write one, make sure to open-source it. :-)
Since datakitchen is a Django application, we decided to use the python-escpos library which supports most features of the printer.
Some essential functions are not supported, though: Getting the printer status, which is important if you want to know if there is something wrong.
I ☐ to print Unicode
datakitchen is a multilingual site, so we needed a way to print umlauts on both the vouchers and receipts.
The printer we've chosen (EPSON TM-20 series) does support printing umlauts, but we went with a different solution. Why, you might think?
Because although the printer does support umlauts if used correctly, it only supports a small subset of character sets. If you need to print unicode and want to use utf-8, you are probably out of luck.
Secondly, you have to include the desired character set in every request sent to the printer -- that is, if you know the target encoding of your text. If you don't, you somehow need to find out. This can be a tricky problem if you're not storing meta-information about the text (charset, or the language).
Getting the language of a text is not trivial, and even if you succeed (maybe by using Natural Language Processing) you'd have to map the found language to the right charset. What if this charset is not supported by the printer?
If you don't go with the charset-route, you have to either convert your text to ASCII or make sure that it is compatible from the start. You can use transliteration to convert your unicode text to ASCII. This can work, but you'd have again should use a good algorithm. We tried using the unicode python package but ended up not using it, because it did not convert german umlauts correctly.
In the end, we chose the option to make sure that everything we send to the printer is ASCII.
If you want to go fancy, one other option would be to render an image containing all of your text (by using cairo, e.g.) and just print that image.
Robots also like Printers
During development we found that the printer was sometimes printing random stuff for no obvious reason. While debugging this issue, it was clear that our application wasn't involved in this. Instead, the print requests were started by crawlers on the internet, like the Google Robot. Because the printer is located in the restaurant, the printer must to be accessible by our servers in various data centers. We didn't think about authenticating the print requests, because the printer is just not supporting it. Our solution was to use a proxy to add authentication to that and only to allow the proxy to access the printer.
Printer is out of paper
How do you check if there is enough paper? As I explained above, querying the printer status did not work using the library we used to send print jobs. We had to find another way to do this. Luckily, the printer's ethernet module exposes another service (running separately from the printer service) that can be used to query desired printer status. For our printer, it is a binary UDP protocol. In order to query the status, we can send a status request, and get back a binary response which basically contains a bitmask that can be used to check the status.
After implementing the module to communicate via UDP, we found that the printer also supported SNMP. So, make sure to RTFM and check if your printer supports it. SNMP a standardized protocol to query/monitor network devices, which we would have used if we found out earlier... If your printer supports SNMP, just use it.
Printing just does not work
No matter how well you did your planning and did everything right, at some point in time things will go wrong. The network might fail, the printer will not respond for some unknown reason, etc. -- you have to have a way to handle such events. Even if your printer has it's own printing queue, it doesn't save you if the job you're sending is not received because the connection broke. You should use a task queue like sidekiq or celery for this. When using a task queue, you can also integrate a retry-mechanism using a backoff strategy that matches your requirements (you should include a jitter).
There are two basic scenarios: if the printer is not reachable, the task system will queue the printing jobs as long as the printer is not available and will retry according to a specific backoff strategy. As soon as the printer is reachable again, the task system will forward the printing task to the printer. If your printer provides a print queue, let the printer do the queueing -- it is designed to do it.
To sum up our recommendations, you should:
- prefer a printer that is supported by a software library in a language you already use.
- If you're building a multilingual site, make sure the printer can handle utf-8 (or similar).
- Account for the amount of text you are planning to print (does it fit?).
- If you're using a printer that is connected to the network, make sure to add some layer of authentication/authorization.
- Prefer printers with a buildin print queue.
- The printer queue only works if the printer is reachable and functional of course, so consider using a task system (like celery) to queue the print jobs. Make sure to implement a reasonable backoff mechanism.
- If you are sick of handling all this nerd problems, let professionals like CosmoCode do the work for you :-)