Posts tagged: pl2303

Serial killers?

Ever since I added the Current Cost to my Ubuntu server, I was running into the problem that upon booting up, the order of my two (one for the Current Cost unit, and one for an APC SmartUPS) Prolific PL2303 serial ports changed. Each boot, the actual device hanging of the /dev/ttyUSB* nodes would be a complete random choice. And that is not good :(

So I investigated writing some udev rules for them, but unfortunately, the PL2303’s are completely identical to the server, except that their position on the USB host would change. And although I tried to find out a consistent way of determining the correct udev rule, I miserably failed. The only way I was able to find out which device was which, was to issue a

$ cat /dev/ttyUSB0

and waiting if some output would appear on the terminal. The Current Cost unit spits out its data every 6 seconds, so if something showed up, that would be the right one to pick for the cacti data source. The UPS would not show anything at all using the above command.

Reading a bit further on udev rules I noticed that you can also use external programs to name devices. This got me thinking. I would need a small program that would listen on the /dev/ttyUSB* node and timeout if nothing was received within a reasonable time.

Let’s start off with the udev rule that invokes the script:

# /etc/udev/rules.d/60-local.rules
# Determine APC UPS and Current Cost USB ports
KERNEL=="ttyUSB*", \
    ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", \
    PROGRAM="/usr/local/bin/usb-dev-test.pl %k", \
    SYMLINK+="%c"

The %k parameter will be ttyUSB0, ttyUSB1, etc, and the script usb-dev-test.pl should output a single word, which will be substituted by udev in the %c parameter. With the help of Device::SerialPort it becomes trivial ;)

#!/usr/bin/perl
# /usr/local/bin/usb-dev-test.pl
 
use Device::SerialPort qw( :PARAM :STAT 0.07 );
 
my $argument = $ARGV[$0];
my $port=Device::SerialPort->new("/dev/$argument");
my $STALL_DEFAULT=8; # how many seconds to wait for new input
my $timeout=$STALL_DEFAULT;
 
$port->read_char_time(0);     # don't wait for each character
$port->read_const_time(1000); # 1 second per unfulfilled "read" call
 
my $chars=0;
my $buffer="";
while ($timeout>0) {
  my ($count,$saw)=$port->read(255); # will read _up to_ 255 chars
  if ($count > 0) {
    $chars+=$count;
    $buffer.=$saw;
 
    # Check here to see if what we want is in the $buffer
    # say "last" if we find it
    last;
  }
  else {
    $timeout--;
  }
}
 
if ($timeout==0) {
  print "apcups";
}
else {
  print "currentcost";
}

And look at the result… Magic!

$ ls -la /dev/ttyUSB* /dev/currentcost /dev/apcups
lrwxrwxrwx 1 root root         7 Jan 26 13:10 /dev/apcups -> ttyUSB0
lrwxrwxrwx 1 root root         7 Jan 26 13:10 /dev/currentcost -> ttyUSB1
crw-rw-rw- 1 root dialout 188, 0 Jan 26 14:09 /dev/ttyUSB0
crw-rw-rw- 1 root dialout 188, 1 Jan 26 11:54 /dev/ttyUSB1