Reviving an OKI Microline 320 Elite dot matrix printer (Part I)

I have an unhealthy obsession with printing, typewriters and printers. The best cure for this horrible affliction is, of course, to stimulate it by bringing an ancient, but hardly forgotten, relic of our digital times back to life: the OKI Microline 320 Elite; for short ML320EL.

This marvel of reliability has your standard 9 pin head design of the era, but with a NLQ (Near Letter Quality) mode which can reproduce the printing quality of the daisy wheel printers, but without being limited to the relatively limited range of characters of the daisy wheels. It is also a cheaper alternative to the pricier, but higher output quality 24 and 36 pin dot matrix printers, with the limitation of not being able to reproduce Chinese characters and other glyphs that the low resolution of a 9 pin head would absolutely butcher.

But why even attempt to revive this noisy metal box apart from the fun factor? Well, you might not remember, but paper is an excellent medium to convey information. Snark aside, we do have a lot of mediums that can convey text well. Laser printers are better and sharper than ever before and we have e-ink devices that have ridiculously low power requirements.

The problem with both of these is that they both produce e-waste. Sure, you could buy them used, and you should, but say for example laser printers; they require much more expensive toners and drum replacements than a dot matrix printer would with its ink ribbon. The mean time to failure (MTTF) is also so much lower in comparison to a laser printer. Of course, printing anything than text is not ideal on a dot matrix printer, so pros and cons kinda equal themselves out depending on your purpose.

Nevertheless, repurposing old hardware is what the cool kids do, so let's get started.

Abstracting away control codes

The abstraction for the control codes can be regarded effectively as the “connection libraries” that are commonly used with many serial thermal printers used in maker project. The Adafruit thermal printer library is a great example of this.

There are three representations of the control characters; text (referred to as ASCII in the printer handbook), decimal and hexadecimal; the two latter being the alternatives of what one should be sending to the printer as sending the ASCII control characters as text will not work; they are interpreted exclusively as text and not commands.

While hexadecimal is the most common way of representing control codes in other projects, consider the following example in Python.

HEXADECIMAL = [0x1b,0x35,0x30]

DECIMAL = [27, 53, 48]

These two lists of numbers are equivalent, however, one of them takes considerably less time to write and is, in my opinion, quite more pleasing to the eye. So before you start malding about how hexadecimal is the true representation for binary data, consider that most reasonable people would just prefer to write less and that is actually the chad position in this case. The printer handbook also confirms this by using decimal as well.

In the printer handbook which is included with the printer, examples of sending commands are provided in BASIC. Sending the ASCII directly in BASIC require the CHR$(n) string function which converts either the decimal or hexadecimal integer (n) to its corresponding bytes representing an ASCII character.

Similarly, in Python, one may use bytes([n1, n2 ... nth]) to encode a list of integers into bytes, which will be interpreted control codes.

A wild variable appear

My initial idea was to have a list of all the control characters as constants in a Python file, and that would be sufficient for absolutely all the control codes. OKI decided to throw an “ackchyually” into the mix and now some control codes cannot be constants as they contain variables. For example, when one wants to select a code page you would send the following sequence:

27 91 84 5 0 0 n1 n2 0

The escape character, ESC, is 27. n1 and n2 are the variables used to select the code page. To find the ID number of a code page in IBM emulation mode, you would have to take the IBM ID number and, using floor division, divide it by 256 and assign this number to n1, and the remainder to n2. The IBM code page for Norway is 865, hence n1 = 3 and n2 = 97.

Having a list of constants that people could reuse is therefore not as feasible or useful as first theorised. Therefore, the final design of the library ended up looking like the many other “DIY printer” libraries as first mentioned in the opening of this post.

By instantiating the printer as an object with its control codes as methods, we're also able to retain some data on what the previous and print mode are. If we also consider that it's useful to be able to print to several different printers at the same time within the same script, this way of doing things make much more sense. Documentation can also be provided in the docstrings as an added bonus.

Control code compatibility

In addition, there is the question of printing mode compatibility. There are three compatibility modes, namely P, G and F; Proprinter XL mode, IBM graphics printer mode, and Epson FX mode respectively.

I will only implement IBM graphics mode emulation for now. If anyone needs any other mode, I suppose could try to implement those as well. I would assume that many of the control codes are marginally different with different (but similar) OKI dot matrix printer models, but that the functionality would be quite overlapping. I would need to look into this matter further, but it would probably be possible to create a printer library that would work on a range of different dot matrix printers with the same functionality. If the project ever comes to this, the control code abstraction library should probably be released on pypi or something.

Going forwards

So that's as far as I have gotten for now. Some rudimentary programs are working on my local machine, but they're basically just in the proof of concept stage. I might put at least the library of the control codes in a git repository at a later point, but it's neither complete nor tested.

Some of the hurdles I have to get over is that I want this printer to work out of the box with minimal amount of messing around to get it to work. That requires the code to work, of course, but without documentation code is worthless. I have little to no documentation now apart from research notes, so that also have to be significantly improved.

The ironic part is that it's actually Linux that is fighting me at this point. In comparison to getting the printer to work on Windows, getting it to work universally on a wider set of Linux environment isn't as straight forward as I like it to be. Before you ask, yes I've tried to use CUSP (even though I didn't really want to) but the printer only works with the older version of CUSP. That is not ideal to me.

Sending the data directly to the printer through shell commands isn't ideal either as you have to send NULL bytes and bash doesn't like that. Sure, I could use zsh but keep in mind I want this stuff to work out of the box and installing a new shell wasn't supposed to be a part of that. Hopefully there is another way to fix it that I haven't considered.

So yeah, for someone who appreciate things that Just Works™, there is indeed plenty of work to do here.