Google Calendar Sprinkler Controller

The sprinkler controller described here applies to my previous house. The controller worked flawlessly for four years. The new owners of the house wanted a traditional controller, so it was uninstalled and replaced.

When I bought my house, the gardens attached to it had reticulation in them already, which was great. The lawn was not reticulated, but it turns out that an appropriate hose had been placed near the lawn, but never hooked up to sprinklers. So it wasn't too hard a job to reticulate the lawn as well.

However, the one thing about the reticulation is that there were a series of valves. You turned on the valve that you wanted, and then the tap, and then the garden got watered.

Which is all good and well. In Perth we have some serious (and much needed) water restrictions relating to the watering of gardens. You can only water your garden two days a week, on a roster based on the last digit on your house number. (My last digit is 5, which makes my watering days Sunday and Wednesday). The other restriction is that you have to water before 9AM, or after 6PM - and only in one of those two intervals for that day (ie, you can't water Sunday morning and Sunday night too).

I personally got irritated by having to manage the sprinklers. You'd have to turn them on, time them for a few minutes, and then switch to another station. Being the lazy person that I am, I figured I had to automate the whole system.

So that's what I did. The current system is probably what you would call version 2.

Some parts of the system were scavenged parts. That is why the hardware is kinda ugly... but amazingly enough, the system works, and hasn't broken down since its construction.

Features of my System

The current system has these features:

Ideally it would contain a rain sensor here that it collects data from, rather than viewing the rainfall at the nearest government weather station, which is just over 6 kilometres away, but this system works well enough.

Version One

The original version of this sprinkler controller was serially controlled instead. The controller was a small PIC16F series microcontroller, with a simple program I found on the net. You passed it serial bytes which told it which outputs to turn on; and it did that. That was about as complicated as the controller got.

I actually used a PIC experimenters kit that I built quite a few years ago as the base for the project. I connected the serial output to my terminal server, so that I could ethernet-enable this sprinkler controller (I have an ancient Lantronix ETS16P terminal server - it was a cheapy on Ebay).

Other than that, version one did not differ from the current version.

Hardware

Solenoids and plumbing

At the local Bunnings I acquired four water solenoids. They are 24VAC soleniods that you just connect inline to the pipes in question. I bought ones that matched the pipes I already had, so they were pretty easy to hook up.

You can see them in the photo below.

Sprinkler Manifold with solenoids (110k)

The valves were existing, and I just cut the pipes at the appropriate spots and inserted the solenoids. Originally I just had the three solenoids - but I quickly found out that it was impossible to prevent the supply side from leaking, no matter what I did to it. So I then added the master valve, and kept whatever was on the supply side of that as tight as possible. It still leaks a little, but not much at all (much, much less than a dripping tap).

Because of the master valve, you have to open one of the other valves first (to select what to water) and then open the master valve. Turning the sprinklers off is just reversing this proceedure.

Relay board

As the solenoids were 24VAC, you can't just hook a pin from a PIC up to them and have it work (PICs are pretty good, but that's a little out of their range). So, you need relays to turn on the solenoids.

And then you need a 24VAC power source. Which are, surprisingly, not that common (at least, when you're scrounging for parts). However, I have a weakness for fairy lights - and as such have quite a number of sets of fairy lights. Naturally, some of these are really cheap and nasty - and poorly made. I had one such set of lights where 60% of the lights stopped working (and yes, I did go through and replace globes to no avail) - so I chucked them. However, it conveniently had a 24VAC plugpack to power the lights... so I appropriated it for this project.

Then comes the relay board. I had some relays sitting in my cupboard that I had pulled out of some other piece of equipment a long time ago. They had a 9VDC coil, but could switch up to 110VAC through them. Perfect!

I also needed to step down the 24VAC to 9VDC for the relays, and also for the ethernet controller board (which can take 7-35V DC, but not AC). So I put together a simple 7809-based regulator circuit to step the voltage down. (The version of the regulator circuit shown in the photos is hardware version 2 - I managed to blow up the first version, but that was me being silly and just soldering the parts together, and not to veroboard like you see in the photos.)

Below is a schematic for the power supply:

Power Supply Schematic (18k)

You still can't power a relay from a PIC pin. So I dug up a few NPN transistors, a few resistors (all spares in my cupboard), a bit of veroboard, and created the monstrosity that is my relay board.

Below is a schematic of what the relay board looks like. Only one of the four identical relay circuits is shown.

Sprinkler Relay Schematic (31k)

Below is a photo of the relay board, on both sides. As you can see, it's really, really ugly. Pretty much to the point where I'm embarassed to publish it on the internet.

Top view of relay board (69k) Bottom view of relay board (96k)

Here is the power supply (before it got converted into the cooling stack, more details on that later):

Power supply for relays (37k)

Ethernet Controller board

The ethernet controller board is a SBC45 from Modtronix, in Queensland, Australia. It is a neat little board - comes with a PIC18F452, a 32k serial EEPROM, RTL8019 ethernet controller. It also has a chip to bring the RS232 outputs up to RS232 levels.

Below is a photo of the board itself.

SBC (86k)

Below is a photo of the soldered connections to the board.

SBC with soldered outputs (93k)

Hooking it all together

Connecting the relay board to the ethernet controller was quite simple. I chose to solder the wires between the two. Not quite sure why, but I don't expect to make too many changes.

After that the rest of the connections are fairly straightforward. Below is a photo of all the parts connected together, on the bench.

Sprinkler Controller Boards connected together (56k)

Installation into a weatherproof box

This is yet another ugly part of the project. I stuffed all the electronics into a weatherproof box, so it could be mounted outside, near the solenoids that it controls.

The boards are held down by sticky pads that you can put cable ties through. No doubt this is going to fall apart inside the box, probably during summer...

Below is a photo showing the layout of the parts inside the box.

Sprinkler Controller in weatherproof box (79k)

The Cooling Stack

You can see in the photos up until this point that I just bolted a lump of aluminium to the 7809 regulator on the power supply in an attempt to keep it cool. However, at idle, the plate still got quite hot to the touch - hot enough that I couldn't hold it with my fingers for more than a few seconds. Not really something you want to have inside the box...

And why, you may ask, did it get hot if it isn't drawing much power? When you do the maths on it - the incoming voltage is 24VAC, which we convert to DC. Due to the poor regulation of the plugpack, it's more like 30VAC incoming. Once rectified, it's still 28VDC. The 7809 regulator then has 28VDC on one side and 9VDC on the other side. If it draws up to 1 amp of current (as it is designed to do at peak), it has to dissipate 19W of heat. (28V - 9V times 1A). At idle (say 100mA) it only dissipates 1.9W of heat - still enough to require some better heatsinking.

My final solution required another hole in the box. I got a lump of 40mm2 steel tube, and bolted that to the top of the box. The 7809 is then bolted to the inside of this tube. This does the job and dissipates all of the heat. After installation, the system survived a 44°C day, so I figure the cooling is adequate.

Below are a few construction photos of it. I chose to move the capacitors into the stack too, but the ends were covered with heatshink before they were placed into the tube, to prevent any shorts.

Sprinkler Stack, Side (35k)
Sprinkler Stack, End (47k)
Sprinkler Stack, On box (46k)

Final Installation outside

The whole setup was eventually fixed to the wall near the sprinklers. I placed it high up so it was just under the eaves, to keep it out of the sun.

In the end I decided to feed both power and ethernet via two CAT5 cables. Both cables run back to the rack in my storeroom - one was terminated at both ends with RJ45 jacks and hooked up to the ethernet switch. The other cable had a DC power connector soldered on to it at the rack end - to prevent me trying to plug it into a RJ45 socket - and then had the transformer plugged into that. This also keeps the transformer inside and away from the weather (it was an 'indoor only' transformer anyway).

Below are two photos of the setup, one before I siliconed up all the holes, and another after the job was complete.

Sprinkler Controller on Wall (114k)
Sprinkler Controller On Wall (80k)

Software

On the ethernet controller board

The ethernet controller board already comes with a built in webserver and set of pages.

You can download new web pages to it via FTP, so I just replaced the existing index.htm, and added an index.cgi. These pages are really simple - they just provide buttons to toggle each sector, and also a button to turn all outputs off.

Sprinkler Webpage screenshot (42k)

The firmware that comes with the controller board is not 100% perfect, though. I was hoping to get away without having to flash the controller, but I was unable to do so.

In the end, only two changes were required to the firmware. The first one was to initialise all the outputs to off when the system started. Before I made this change, the outputs would randomly be on or off when the power was applied to the system - obviously unacceptable, as you could easily have the sprinklers turn on, and the system wouldn't realise!

The other change I made was to the inbuilt FTP server. It didn't respond to the BIN command, which meant that UNIX ftp clients tried to upload the files in ASCII mode, which resulted in a broken web pages image. After this change, the system worked perfectly.

On the server

Cron Entries from Google Calendar

To get the cron entries from Google Calendar, I hacked together a python script to download an iCal (.ics) version of the relevant calendar, and then parse it, and create cron entries.

When I wrote it I was being quite lazy and I looked around for an iCal library. There doesn't seem to be too many good complete libraries for iCal. In the end I found one that would parse an iCal file, and then be able to answer the query, "are there any events X days from now". So I do this from tomorrow up to seven days from now, and parse those events into appropriate crontab entries - absolute entries, with the date in them. (So they are all one-shot).

A quick python module to read and write the current users crontab allowed for relatively simple modifiation of the users crontab. The entries are in their own comment-delimited section at the bottom of the users crontab, leaving other existing entries untouched.

This script is kinda wierd: in one sense, it's way underengineered; and in another sense, it is way overengineered. What happens to the calendar events is described in a ini file, which in my case looks like this: (as you can see, this is the overengineered bit)

[global]
daysahead = 7
[calendars]
watering = http://www.google.com/calendar/ical/[snip - contains private UID here]/basic.ics
[commands]
watering = sprinklers raincheck
[cmd-sprinklers]
# match contains named match groups: (?P<foo>.*)
# oncommand contains printf string for python, where %(foo)s is
# replaced with named "foo" from match
# Special items available: (these take precendence over the ones from match)
# calendar = symbolic name of calendar (from above)
# date = date of run (ontime) (string in YYYY-MM-DD)
# time = time of run (ontime) (string in HH:MM:SS)
# offdate = offdate (string in YYYY-MM-DD) (Only if offcommand is set)
# offtime = offtime (string in HH:MM:SS) (Only if offcommand is set)
# duration = duration (string in seconds, eg, 3600)
match = Garden Water (?P<area>\w+)
oncommand = /home/daniel/sprinklers/sprinkler-control-wrapper %(area)s on
offcommand = /home/daniel/sprinklers/sprinkler-control-wrapper %(area)s off
[cmd-raincheck]
match = Rain Check
oncommand = cd /home/daniel/sprinklers; ./rain-last-few-days.py

This results in the following crontab for my user:

# Update the local crontab from Google Calendar. 00:10 each day.
10 0 * * * cd /home/daniel/icaltocron; ./icaltocron.py
# ICALTOCRON-START
# Automatically generated - if you edit, any changes will be removed
# next time icaltocron is run.
27 6 23 9 * /home/daniel/sprinklers/sprinkler-control-wrapper frontgarden on
37 6 23 9 * /home/daniel/sprinklers/sprinkler-control-wrapper frontgarden off
16 6 23 9 * /home/daniel/sprinklers/sprinkler-control-wrapper backgarden on
26 6 23 9 * /home/daniel/sprinklers/sprinkler-control-wrapper backgarden off
5 6 23 9 * /home/daniel/sprinklers/sprinkler-control-wrapper lawn on
15 6 23 9 * /home/daniel/sprinklers/sprinkler-control-wrapper lawn off
0 6 23 9 * cd /home/daniel/sprinklers; ./rain-last-few-days.py
27 6 26 9 * /home/daniel/sprinklers/sprinkler-control-wrapper frontgarden on
37 6 26 9 * /home/daniel/sprinklers/sprinkler-control-wrapper frontgarden off
16 6 26 9 * /home/daniel/sprinklers/sprinkler-control-wrapper backgarden on
26 6 26 9 * /home/daniel/sprinklers/sprinkler-control-wrapper backgarden off
5 6 26 9 * /home/daniel/sprinklers/sprinkler-control-wrapper lawn on
15 6 26 9 * /home/daniel/sprinklers/sprinkler-control-wrapper lawn off
0 6 26 9 * cd /home/daniel/sprinklers; ./rain-last-few-days.py
27 6 30 9 * /home/daniel/sprinklers/sprinkler-control-wrapper frontgarden on
37 6 30 9 * /home/daniel/sprinklers/sprinkler-control-wrapper frontgarden off
16 6 30 9 * /home/daniel/sprinklers/sprinkler-control-wrapper backgarden on
26 6 30 9 * /home/daniel/sprinklers/sprinkler-control-wrapper backgarden off
5 6 30 9 * /home/daniel/sprinklers/sprinkler-control-wrapper lawn on
15 6 30 9 * /home/daniel/sprinklers/sprinkler-control-wrapper lawn off
0 6 30 9 * cd /home/daniel/sprinklers; ./rain-last-few-days.py
# ICALTOCRON-END

You can download the whole icaltocron script.

Determining if there was enough rain

To determine if there was enough rain, we look at the rain for the last few days at the nearest government weather station. The Australian federal government kindly puts the last few days of observations (direct from the weather station) onto the web. These are not quality controlled, but are really just raw observations.

(Disclaimer: I used to work for the Australian Bureau of Meteorology. That's how I know about these things.)

So I simply screen-scrape the page, tally up the rain for the last few days, and if it is over a certain threshhold, the sprinklers dont get turned on. As you can see, there are lots of hardcoded things in the script - bad idea - and also, all it does is write a file, whose contents is the amount of rain for the last few days, if it is over the built in threshold of 5mm.

To make this trivial, I am using the BeautifulSoup library for Python. It makes screenscraping mostly-trivial for Python. (One of my past jobs was to write screenscrapers, and Python was my favourite for writing them).

#!/usr/bin/env python
from BeautifulSoup import BeautifulSoup
import urllib2
import os
# URL for Jandakot airport last few days obs.
URL = "http://www.bom.gov.au/products/IDW60801/IDW60801.94609.shtml"
FLAGFILE = "/home/daniel/sprinklers/enough-rain"
try:
os.unlink(FLAGFILE)
except OSError:
# File not found... ignore...
pass
page = urllib2.urlopen(URL)
soup = BeautifulSoup(page)
totalrain = 0.0
steprain = 0.0
# For each table with data in it.
tables = soup.findAll('table', attrs={'class': "tabledata"})
tables.reverse()
for table in tables:
rows = table.findAll('tr', attrs={'class': "rowleftcolumn"})
rows.reverse()
for row in rows:
cell = row.findChildren()[13]
try:
rain = float(cell.contents[0])
except ValueError, e:
# Hmm... invalid input.
rain = steprain
if rain > steprain:
steprain = rain
if rain < steprain:
totalrain += steprain
steprain = 0.0
# And now we have the rain for the last few days.
# More than 5mm? Set our "too much rain" flag.
if totalrain >= 5.0:
f = open(FLAGFILE, 'w')
f.write(str(totalrain))
f.close()

You can download the script here. If you want to use it (in Australia, at least) you can just find your nearest weather station and replace the URL in the script with that one.

Toggling sprinkler outputs

With the old serial controller it was much harder to toggle bits, because I had to open a TCP socket to the terminal server, and then write down a series of bytes (binary values) to tell it what outputs to turn on. I (for some odd reason) decided to do this in a PHP script (I can't recall why). Fortunately, with the new ethernet controller, this is no longer a problem - all the outputs can actually be toggled with just a shell script - with the help of wget.

And to toggle the outputs, I ended up with the following shell script. As you can see, it's pretty ugly, and uses a file to let itself know it is already turned on.

#!/bin/bash -e
# TODO: This is hardcoded...
CONTROLLER="10.41.61.10"
ONFILE="/tmp/sprinklers-lock"
MASTERVALVE="4"
LAWNVALVE="0"
FRONTVALVE="1"
BACKVALVE="2"
function valveon {
wget -O /dev/null -o /dev/null http://$CONTROLLER/ioval.cgi?B$1=0
}
function valveoff {
wget -O /dev/null -o /dev/null http://$CONTROLLER/ioval.cgi?B$1=1
}
: ${1?"Usage: $0 <area> <on|off> Areas: lawn, backgarden, frontgarden"}
if [ "$1" = "clean" ];
then
valveoff $MASTERVALVE
valveoff $FRONTVALVE
valveoff $BACKVALVE
valveoff $LAWNVALVE
rm -f $ONFILE
fi
echo -n "Turning "
echo -n "$1"
echo "$2"
if [[ "$1" = "lawn" || "$1" == "backgarden" || "$1" == "frontgarden" ]];
then
if [ "$1" = "lawn" ];
then
THISVALVE="$LAWNVALVE"
fi
if [ "$1" = "backgarden" ];
then
THISVALVE="$BACKVALVE"
fi
if [ "$1" = "frontgarden" ];
then
THISVALVE="$FRONTVALVE"
fi
if [ "$2" = "on" ];
then
if [ ! -e "$ONFILE" ];
then
echo "$1" > "$ONFILE"
valveon $THISVALVE
valveon $MASTERVALVE
else
echo "Sprinklers already on!"
echo "Try with 'clean'"
fi
else
valveoff $MASTERVALVE off
valveoff $FRONTVALVE off
valveoff $BACKVALVE off
valveoff $LAWNVALVE off
rm -f "$ONFILE"
fi
else
echo "Invalid area name."
fi

You can download the whole script here.

Combining the rain measurement with the sprinklers

The sprinkler-control script itself has no means to check if there has been too much rain - as it should be; because this should be able to be called by itself, to turn the sprinklers on manually.

So, for that reason, a wrapper script was written to check how much rain there was, and then turn the sprinklers on, if relevant.

#!/bin/sh
# Only start the sprinklers if it has not rained too much
# over the last few days.
RAINFILE="/home/daniel/sprinklers/enough-rain"
CONTROL="/home/daniel/sprinklers/sprinkler-control"
if [ "$2" = "on" ];
then
if [ ! -e "$RAINFILE" ];
then
$CONTROL $1 $2
else
RAINQTY=`cat "$RAINFILE"`
echo "Not turning on sprinklers - seen $RAINQTY mm in last few days"
fi
else
$CONTROL $1 $2
fi

You can download the whole script here.

Fun with Sprinklers

One year someone organised an easter egg hunt at my place. It turns out I was the one who went and hid all the eggs in my backyard, and then let everyone loose to go and find them.

And there is only one way to make an easter egg hunt interesting: turn the sprinklers on. Except I didn't want to run back inside the house to do it (too obvious, and I miss the fun)... and I also wanted randomness. So, I knocked up a few more lines of python code to do this for me.

#!/usr/bin/env python
import random
import datetime
import time
import os
random.seed()
while 1:
nextFire = random.randrange(1, 4)
fireCircuit = random.randrange(1, 3)
now = datetime.datetime.now()
then = datetime.datetime.now() + datetime.timedelta(0, nextFire * 60)
print "Now: %s firing %s" % (now.strftime("%H:%M:%S"), then.strftime("%H:%M:%S"))
time.sleep(nextFire * 60)
if fireCircuit == 1:
os.system("./sprinkler-control lawn on && sleep 5 && ./sprinkler-control lawn off")
else:
os.system("./sprinkler-control backgarden on && sleep 5 && ./sprinkler-control backgarden off")

The script will choose a random time period, between 1 to 3 minutes, and then choose one of the sprinkler areas to turn on (either the lawn or the back garden, the two locations where people were picking through for eggs). If the sprinklers came on, they would only be on for 5 seconds (enough to dampen people caught in it, but not waste too much water).

It ended up running for about 30 minutes whilst people were looking for eggs. I actually found out afterwards that it had a bug - only the lawn sprinklers ever turned on. Originally, when choosing the fireCircuit value, the range was 1,2 - which meant that it would only ever return 1. Oops. It still worked ok, because my lawn was very unruly at this point, so they kept finding eggs in it until they stopped looking.

This actually occured with version 1 of my sprinkler controller, but it would work with no changes in the current version of the hardware.

You can download the script here.

Results

This document has been many months in the making. As a result, I've had an automated sprinkler system for around 18 months now. It has gone through two hardware revisions, and I'm really happy with the system. It really does look after itself - if I happen to be awake at 6AM, I can hear the sprinklers going off exactly like they are supposed to... and then turn over and go back to sleep until I really have to get up.

Since putting the system together, I've had no failures - it has always worked if it was supposed to work. Which is my kind of system - it just works! No doubt there will be a day when it fails to work, but I'll deal with that when it happens.

The only other things to note are the uglyness of the hardware. As I've said, most of it was built from parts I scavenged from my spare parts bin. If I was building the system for another person, I would clean it up a lot. A suitable relay board can actually be purchased from Modtronix - although the board takes a SBC65 series SBC. In fact, another person used a SBC65 and an IOR5E board from Modtronix into a sprinkler controller of their own which is much neater than my solution. He also modified the firmware on the device to make the device enforce the on-time of the relays.

Other than that, enjoy...