I’ve got an old Raspberry Pi (version 1B) controlling my pet snake’s vivarium. Specifically, it controls power to the lights and heaters and collects temperature data. It’s nothing fancy, but this is my website, yeah?

I’d like to think that this is largely an example of stitching together simple utilities to achieve complex tasks. The PID loops in Python are a little in violation of that idea, but nevertheless, the aspiration remains.

Power Control Hardware

Hard Power

The device used is a “Western Telematic Inc.” “RPB+” (the plus sign is dramatically important; the “RPB” without plus is a remarkably dumb device). It speaks (an oddly-specific) RS232 out one side and has five standard 110V power ports on the other. Its manual is available at http://www.wti.com/guides/rpbp_b.pdf .

To make Linux reliably use this device, it’s necessary, apparently, to configure the serial port in a rather particular way

stty -F /dev/ttyUSB0 0 || true
stty -F /dev/ttyUSB0 sane -echo clocal crtscts hupcl -icanon icrnl inlcr 9600

The first line will hangup the port (lower DTR) and yet will report an error (“stty: /dev/ttyUSB0: unable to perform all requested operations”) which must be ignored. I cannot fathom. In any case, the second line causes the kernel to…

  • bring the serial port into a sane state, but…

  • turn off character echoing (very important or we echo responses as commands)

  • wait for carrier

  • use RTS/CTS flow control

  • assert DTR when there’s a connection open

  • turn off canonical mode (e.g. line buffering)

  • convert some line ending characters on the way in

  • set the baud to 9600

Phfew. At that point, we can speak to the device using the tools we might expect (e.g., socat STDIO /dev/ttyUSB0).

Every now and again the initial status report fails to make it all the way to the prompt. Press enter and “/S” again if you want it; it always seems to work the second time.

Soft Power

Having hard relays driving the lights is fine, and it’s fine for some of the heaters, too, but it’s not so good for the under-tank heaters that are used to regulate hide temperature. Full power or full off just is too extreme, resulting in several degree overshoot in both directions that just seems stressful. So I got a DMX-controlled dimmer (a Chauvet DMX-35 4-channel dimmer pack) and a USB to DMX adapter (a DMXking ultraDMX Micro) so that these more demanding devices can be handled in a sane way.

Additional Hardware

Camera

There is, additionally, a raspberry pi camera module, on a 5 meter ribbon cable, with a wide-angle lens mounted to the top of the tank. This lets me take pictures of Melman and eventually watch for motion and record segments.

LCD

I have an I2C LCD connected and running lcdproc. I had to build 0.5.8-rc2 from source because this backpack is wired differently than the old driver expects and the version with configurability is not in Debian.

All told, the LCDd.conf stanza for this display looks like:

[hd44780]
ConnectionType=i2c
Device=/dev/i2c-1
Port=0x27
Backlight=yes
Size=20x4
DelayBus=false
DelayMult=1
Keypad=no
Speed=0
i2c_line_RS=0x01
i2c_line_RW=0x02
i2c_line_EN=0x04
i2c_line_BL=0x08
i2c_line_D4=0x10
i2c_line_D5=0x20
i2c_line_D6=0x40
i2c_line_D7=0x80
BacklightInvert=yes

Power Control Software

Hard Power

A simple expect script suffices to drive the hardware. The lights are controlled by remind, a remarkably flexible scheduling program. I have it running under runit automation (which, in turn, is running under systemd because I am on a Debian box) using a run file of:

#!/bin/sh
exec chpst -u pi:pi:dialout remind -z /home/pi/.remind

And the remind script itself, at /home/pi/.remind/sc.remind is:

SET $LongDeg 76
SET $LongMin 36
SET $LongSec 33
SET $LatDeg 39
SET $LatMin 17
SET $LatSec 33

REM * AT [sunrise()] RUN /home/pi/sc/rpb.expect 1 on >/dev/null
REM * AT [sunset()]  RUN /home/pi/sc/rpb.expect 1 off >/dev/null

The expect script /home/pi/sc/rpb.expect is mercifully short:

#!/usr/bin/expect
proc waitprompt {} {
  expect {
    "RPB+> " { }
    timeout { exit 1 }
  }
}
spawn /home/pi/sc/serial.wrap /dev/ttyUSB0
set timeout 70
send "\n"
waitprompt
if {$argc == 2} {
  set outlet [lindex $argv 0]
  set cmd [string toupper [lindex $argv 1]]
  send "/$outlet $cmd\n"
  set timeout 30
  waitprompt
}

and the serial wrapper /home/pi/sc/serial.wrap too:

#!/bin/sh
set -e -u
stty -F $1 0 2>/dev/null || true
stty -F $1 sane -echo clocal crtscts hupcl -icanon icrnl inlcr 9600
exec flock -e -w 60 /var/lock/LCK..`basename $1` socat $1 STDIO

The use of a lockfile is necessary because Linux doesn’t believe in exclusive access to serial devices. (Or, well, truthfully, it does, but there are enough caveats to “exclusive” that it isn’t worth attempting to use robustly.)

Soft Power

The DMX outputs are driven by PID loops that watch the temperature sensors. Eventually I will release the code repsitory, I just haven’t done it yet, sorry.