Driver
There are essentially three layers to this.
- CUPS driver - scripty goodness, postscript to PNG
- Java driver - PNG to serial commands over USB, independent of CUPS
- Firmware - C++ on wiring board, serial commands to pen movements.
CUPS driver
CUPS, the common UNIX print system defines an architecture for printer drivers. This is used by at least Linux and Mac OS X.
The simplest way to use this is to define a new "backend". The backend perform two functions; device discover and passing print jobs to the printer. There are various protocols which can be chained together automatically, including PCL, printer command language, postscript to raster etc. However these are all overkill for my requirements.
Instead the backend driver receives the postscript directly (via a pipe), and then calls ImageMagik to convert the .ps file into a series of .png files.
#!/bin/bash if [ ! "$1" == "" ]; then # Standard args passed by CUPS id=$1 user=$2 title=$3 copies=$4 opt=$5 file=$6 echo "Printing..." > backend.log echo "id = $id" >> backend.log echo "user = $user" >> backend.log echo "title = $title" >> backend.log echo "copies = $copies" >> backend.log echo "opt = $opt" >> backend.log echo "file = $file" >> backend.log # work in a temp directory tmp=$( mktemp -d -t lego ) cd $tmp # Redirect the piped postscript to a file cat /dev/stdin >> print.ps # ImageMagik the postscript to png. export PATH=/opt/local/bin:$PATH convert print.ps print.png &> backend.log cp=/usr/libexec/cups/backend/lego110driver /usr/bin/java -Dhorse.pwd=$PWD -Djava.awt.headless=true -cp $cp/lego110.jar:$cp/RXTXcomm.jar com.adamish.lego110.Main *.png >> backend.log else # CUPS printer discovery echo "direct lego110://horse \"Lego felt tip 110\" \"Lego felt tip 110\""; fi
Installation
On Mac OS X 10.6 the cups backends directory is /usr/libexec/cups/backend.
Also useful to note, the working directory of the script is rooted at /private/var/spool/cups/tmp
It is important that the ownership of the script are set correctly or CUPS will refuse to execute it.
horse:backend adam$ ls -l lrwxr-xr-x 1 adam 501 69 5 Nov 21:45 HP-Fax -> /Library/Printers/hp/Fax/HPFaxBackend.app/Contents/MacOS/HPFaxBackend -rwxr-xr-x 1 root wheel 122384 6 Nov 06:51 bluetooth -rwx------ 1 root wheel 57344 17 Dec 01:34 dnssd -rwxr-xr-x 1 root wheel 54880 9 Jul 2009 epsonfirewire lrwxr-xr-x 1 root wheel 68 23 May 2010 fax -> ../../../../System/Library/Printers/Libraries/Fax/Contents/MacOS/Fax lrwxr-xr-x 1 root wheel 3 23 May 2010 http -> ipp -rwx------ 1 root wheel 106096 17 Dec 01:34 ipp -rwxr-xr-x 1 root wheel 1630 14 Feb 14:03 lego110 drwxr-xr-x 4 root wheel 136 14 Feb 14:01 lego110driver -rwx------ 1 root wheel 93024 17 Dec 01:34 lpd lrwxr-xr-x 1 root wheel 5 23 May 2010 mdns -> dnssd -rwxr-xr-x 1 root wheel 61008 4 Jan 06:22 riousbprint -r-x------ 1 root wheel 2796752 4 Aug 2010 smb -r-xr-xr-x 1 root wheel 66576 4 Jan 06:22 snmp -r-xr-xr-x 1 root wheel 75200 4 Jan 06:22 socket -r-xr-xr-x 1 root wheel 91792 4 Jan 06:22 usb
Now when you go to Add printer in System Preferences... the printer will be reported.

If you're having difficulties look at the cups log file.
tail -f /var/log/cups/error_log
Java driver
The CUPS script runs an external Java program to load the PNGs, scale, and issue the necessary commands over a serial link that it creates. This may not be in the true spirit of the CUPS modular architecture, but for prototype purposes it is both quick and robust. Why Java and not your favourite language Ruby, C# or fortran? It's just the language I have most experience with currently.
The image is first loaded using the standard ImageIO.loadImage mechanism. PixelGrabber is then used to to scale the image. The image is rebuilt and returned as a Image type so that scaling, and color reduction can be visually checked easily.
All the serial work is done by SerialDriver which does all the necessary setup and legwork of using the RXTX GNU library.
There were issues where the driver was flooding out the wiring processing board. Therefore a noddy acknowledgement protocol is used, where the firmware replies with "OK" after each command has been completed. This also has the advantage that print jobs can be aborted mid-print without having to reset the printer.
Firmware
The firmware is written in C++, originally C before I realised that the wiring IDE supported it. This uses a simple serial protocol. These are the commands used for printing.
PXXXXXXXXXXX\n | Print a line, each character is intensity, currently only 0 or 1 supported. |
X\n | Reset print head to home position, reset counters. |
FXXX\n | Form feed XXX amount. |
There are a whole load more added in the course of getting the beast to work.
GXXX\n | Goto absolute position. Approx 0 to 640 for A4. |
D\n | Echo debug info to serial. |
KNXXX\n | Set constant N to value XXX |
L\n | Full left. |
R\n | Full left. |