home / projects / legoprinter


There are essentially three layers to this.

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.

if [ ! "$1" == "" ]; then

  # Standard args passed by CUPS

  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

  /usr/bin/java -Dhorse.pwd=$PWD -Djava.awt.headless=true -cp $cp/lego110.jar:$cp/RXTXcomm.jar com.adamish.lego110.Main *.png >> backend.log

  # CUPS printer discovery
  echo "direct lego110://horse \"Lego felt tip 110\" \"Lego felt tip 110\"";


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.

Screen shot of add printer dialog

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.


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.