The Epson Receipt Printer Adventure Part 1: Pretending Text is a Receipt

November 23, 2019 - General Computing, Programming

Since the software that I contribute to as part of my day job involves printing to receipt printers, I’ve been keeping my finger on the pulse of eBay and watching for cheap listings for models I’m familiar with. Recently I stumbled upon a “too good too be true” listing- an Epson TM-T88IV listed for $35. The only caveat that I could see was that it was a serial printer; that is, it used the old-style RS-232 ports. I figured that might be annoying but, hey, I’ve got Windows 10 PCs that have Serial Ports on the motherboard, and a Null Modem Serial cable, how hard could it be?.

Famous last words, as it happened, because some might call the ensuing struggle to be a nightmare.

When the printer arrived, my first act was to of course verify it worked on it’s own. it powered up, And would correctly print  the test page, so it printed fine. Next up, of course, was to get it to communicate with a computer.  I used a Null-modem cable to connect it, adjusted the DIP switches for 38400 Baud, 8 data bits, 1 stop bit, no parity DSR/DTR control, printed the test page again, then installed the Epson OPOS ADK for .NET (as I was intending to use it with .NET). I configured everything identically, but Checkhealth failed. I fiddled with it for some time- trying all the different connection methods and trying to get better results to no avail.

I fired up RealTerm, and squirted data directly over the COM port. I could get text garbage to print out- I tried changing the COM Port settings in Device Manager to set a specific baud rate as well, but that didn’t work.

I had a second computer- my computer built in 2008, which while it didn’t have a COM *port*, It did have the header for one. I took the LPT and COM bracket from an older Pentium system and slapped it in there for testing, and spent a similar amount of time with exactly the same results. I was starting to think that the printer simply was broken, or the Interface card inside it was broken in some way.

Then, I connected it to a computer running Windows XP. I was able to get it to work exactly as intended; I could squirt data directly to the printer and it would print, I could even set up an older version of the OPOS ADK and CheckHealth went through. Clearly, the receipt printer was working- so there was something messed up with how I was using it. I put an install of Windows 7 on one of the Windows 10 PCs I was testing and found I got the same results. Nonetheless, after some more research and testing, it seems like Windows 10 no longer allows the use of motherboard Serial or Parallel ports. Whether this is a bug or intentional it’s unclear. I would guess it was imposed at the same time during development that Windows 10 had dropped Floppy support; people spoke up and got Floppy support back in, but perhaps parallel and Serial/RS-232 stayed unavailable. Unlike that case though they do appear in Device Manager and are accessible as devices, they just don’t work correctly when utilized.

Since the software I wanted to work on would be running on Windows 10- or if nothing else, certainly not Windows XP, I had to get it working there. I found that using a USB Adapter for an RS-232 Port worked, which meant I could finally start writing code.

The first thing was that a Receipt printer shouldn’t be necessary to test the code, or for, say, unit tests. So I developed an interface. This interface could be used for mocking, and would implement basic features as required. The absolute basics were:

  • Ability to print lines of text
  • Enable and Disable any underlying device
  • Ability to Claim and Release the “printer” for exclusive use
  • Ability to Open, and Close the Printer
  • Ability to retrieve the length of a line in characters
  • Ability to Print a bitmap
  • Ability to Cut the paper
  • boolean property indicating whether OPOS Format characters were supported

I came up with a IPosPrinter interface that allowed for this:

From there, I could make a “mock” implementation, which effectively implemented a ‘receipt print’ by sending it directly to a text file.

This implementation can also optionally shell the resulting text data to the default text editor, providing a quick way of testing a “printout”. However, this interface isn’t sophisticated enough to be usable for a nice receipt printer implementation; In particular, The actual code to print is going to want to use columns to separate data. That shouldn’t be directly in the interface, however- instead, a separate class can be defined which composites an interface implementation of IPOSPrinter and provides the additional functionality. This allows any implementation of IPOSPrinter to benefit, without requiring they have additional implementations.

Since our primary feature is having columns, we’ll want to define those columns. a ColumnDefinition class would be just the ticket. We can then tell the main ReceiptPrinter class the columns, then have a params array accept the print data and it could handle the columns automatically. Here is the ColumnDefinition class:

At this point, we just want a primary helper routine within said ReceiptPrinter class. That could be used within that class to handle printing from more easily used methods intended for use by client code:

This implementation also incorporates a shrink priority that can be given to each column. Columns with a higher priority will be given precedence to remain, but otherwise columns may be entirely eliminated from the output if the width of the receipt output is too low. This allows for some “intelligent” customization for specific printers, as some may have less characters per line and redundant columns or less needed columns can be eliminated, on those, but they can be included on printers with wider outputs. The actual ReceiptPrinter class in all it’s glory- not to mention the implementations of IPOSPrinter beyond the text output, particularly the one that actually delegates to a .NET OPOS ADK Device and outputs to a physical printer, will require more explanation, so will appear later in a Part 2.

Have something to say about this post? Comment!