SlugPower - A Slug-Controlled Power Switch

This page describes the hardware and software design of a printer power switch controlled over USB from my Linksys NSLU2, aka Slug. The unit can, however, be controlled from any Linux box, and can switch anything, not just printers.

My NSLU2 acts mostly as a file and print server. I can go for weeks without printing anything, so I want to keep the printer switched off when I'm not using it (it takes about 4W while idle, which must be more than 99% of its total energy consumption). But it's upstairs, and I don't want to have to go up and down stairs once to switch it on and again to collect my printing. So I decided to get a power switch.

There are plenty of power switches out there. Mostly these are expensive things intended for server rooms (see this example) with RS232 or Ethernet interfaces; USB would be more appropriate for the Slug. One cheap USB switch is this one which has been interfaced to a Slug, but it only seems to be available with continental European sockets. So I decided to build my own.

Hardware

For USB control I decided to use the same USB module that I chose for SlugTerm, using the FT245BM USB-to-Parallel chip. I use just one of its 8 output bits to switch the mains power using a triac. Here's a photo of my breadboard prototype:

USB module, 8-pin DIL optocoupler, TO220 triac, neon and  various discrete components on a breadboard

And here's the schematic:

I needed to learn a bit about triacs before building the mains side of the circuit. There is more than you could ever need to know in this document from ON Semi. Here is what I learnt:

The last two points are addressed by putting a series RC filter accross the triac (R7 and C1). See the ON Semi document linked above, in particular tables 6.1 and 6.3, for the theory and suggested component values. The capacitor needs to be a high voltage type of the order of 0.047 to 0.1 uF, and I have found these difficult to obtain: Maplin don't sell them at all any more, and Futurlec only go up to 0.01 uF. The 4.7 nF and 1.8k that I've used seem to work (the circuit is noticeably unreliable without them) but are not ideal. Possible sources include ESR and Rapid Electronics.

If all of that looks a bit complicated (or dangerous!), you could use a solid-state relay. Futurlec seem to have a good range that are quite affordable.

Here's a view of the inside of the finished unit:

Small veroboard, mains connectors, fuseholder, triac bolted to case

Setting up the USB module

The USB module contains a small EEPROM which stores USB vendor and product IDs, and this needs to be programmed. The procedure is identical as for SlugTerm, so I refer you to that documentation rather than repeating it all here. I have chosen vendor EE17 and product 0002. A suitable file for the ftdi_eeprom program is included in the software, see below.

The Software

You can get the code that I've used, as well as the schematics and the EEPROM configuration, using subversion from http://svn.chezphil.org/slugpower/trunk/.

The code makes use of the Ftdi class that I wrote for SlugTerm. Apart from that it's very straightforward. Here is the main program:

// Slugpower main program
// Phil Endecott, July 2006.

#include "Ftdi.hh"

#include <unistd.h>
#include <iostream>
#include <vector>

using namespace std;

void usage(void)
{
  cerr << "Usage: slugpower [0|1|on|off|flash]\n";
  exit (1);
}


int main(int argc, char* argv[])
{
  if (argc!=2) {
    usage();
  }

  string arg = argv[1];

  try {
    Ftdi ftdi(0xEE17, 0x0002, 192000, 0xFF);

    if (arg=="on" || arg=="1") {
      ftdi.write(1);
      ftdi.write(1);
    } else if (arg=="off" || arg=="0") {
      ftdi.write(0);
      ftdi.write(0);
    } else if (arg=="flash") {
      while(1) {
        ftdi.write(1);
        sleep(1);
        ftdi.write(0);
        sleep(1);
      }
    } else {
      usage();
    }
  }
  catch (const char* c) {
    cerr << "Error: " << c << "\n";
    exit(1);
  }
}

Writing the value twice seems to avoid an odd bug where the first value written gets lost under some circumstances.

The slugmake.sh script will build a Slug executable, using a cross compiler. See http://www.nslu2-linux.org/wiki/DebianSlug/CrossCompiling for how to set up a cross compiler for the Slug on a Debian PC.

CUPS Integration

The next step is to get CUPS to turn the power on when a job is submitted. Google found me this page where a setup using an X10 power switch and a parallel printer port is described; this was a good starting point. Basically you need to create a "backend" script in /usr/lib/cups/backend which CUPS will call instead of its normal USB backend; this script will turn on the power and then call the original USB backend.

There are a couple of subtleties. Firstly, CUPS will call the backend with no arguments when it wants to get info about the printer; in this case we don't want to turn on the power. Secondly, CUPS invokes the backend with argv[0] set to its URI for the printer; this ought to be passed to the normal backend using "exec -a", though as you can see I have it hardcoded. In recent version of CUPS it seems that this needs to be in the environment variable DEVICE_URI as well. (If anyone ever builds a version of this that can control multiple printers this would have to be done properly).

After powering on the printer I wait for the /dev node to appear, indicating that the printer has booted and been recognised by the slug's kernel. This assumes that you're running udev.

CUPS will run all of this as user lp, which probably doesn't have permission to access the USB hardware by defualt. A heavy-handed solution is to make the slugpower program setuid root, and this is what I do at the moment. Alternatively you can pass a devmode option to mount when mounting usbfs, but that will make all of your USB peripherals accessible. The optimal solution is to use udev to change the permissions on the usbfs file when it is hotplugged, but I have not tried to establish exactly how to do that.

So this is what I have at the moment:

#!/bin/sh

# Based on http://funderburgs.net/linux/x10printer/x10


if [ $# = 0 ]
then
	echo "direct slugpower \"Slug Powered Printer\" \"Slug Printer Port\""
	exit 0
fi

touchfile=/var/run/slugpower/slugpower

touch ${touchfile}
/usr/local/bin/slugpower on

while [ ! -e /dev/usb/lp0 ]
do
  sleep 1
  touch ${touchfile}
done

export DEVICE_URI="usb://Kyocera/FS-1020D"
exec -a "${DEVICE_URI}" /usr/lib/cups/backend/usb "$@"

You obviously need to adjust it to match your printer name.

You then need to adjust your /etc/cups/printers.conf, as described in the X10 page linked above, to call this backend rather than the real usb backend. Watch out, CUPS will re-write this file removing any comments that you add! I have this:

DeviceURI slugpower

This script touches a file in /var/run/slugpower when it turns the power on. Make sure that this directory is writable by the user that CUPS runs as. Previously I put this file in /var/run/cups, but something seems to change the permissions on that directory each time my slug is rebooted. You can then use a cron script to turn it off after a period of inactivity, as follows:

#!/bin/sh

touchfile=/var/run/slugpower/slugpower

if [ -f ${touchfile} ]
then

  find ${touchfile} -mmin +20 -exec /usr/local/bin/slugpower off ';' -exec rm '{}' ';'

fi

This isn't perfect, as it turns the power off 20 mins after it started printing, even if the document takes more than 20 minutes to print! I can't think of a good way to work around this; any suggestions?

Links