Friday, 24 February 2012

Sunset-sensitive garden lights with awk, bash and the intertubez

Update: Added code to illuminate the garden in the morning as well.

As the darkness moves later by the day, it was time to change the garden light schedule. As a reminder, my garden lights are controlled by a relay card, which is controlled by a Nanode (an Arduino with an Ethernet) which is controlled by a small Linux plug computer. The Linux box runs cron, which sends http requests over curl to the Nanode, which in its turn switches the lights on or off.

Now the easy way to change the schedule would be to edit the crontab. But that would be uncool, since darkness falls at a different time each day. The nifty way would be to have the lights on only between sunset and sleepy-time. So that's how i wanted it.

Step One: when does the sun set.

There are a few Python scripts out there which return the times of sunrise and sunset. As my tiny Linux box didn't have the proper libraries and it's running an ancient Linux (so it can't be easily updated using apt), i couldn't use that. And after all, why duplicate the effort if the effort's been made elsewhere? And when there's nothing to learn in the process?

The answer can be found at Earthtools, using an URL not quite unlike this:

http://www.earthtools.org/sun/$LAT/$LONG/$DAY/$MON/$TZ/$ISDST

To make this work, you'll need enter the values for LATitude, LONGitude, DAY, MONth, TimeZone and whether it IS DST. 

So we need to add a Step Zero, or actually a multiude of them:

  • Where are we?
  • What date is it?
  • Is it Daylight Savings Time?

The last two questions can be answered with some bash scripting, the date command and a line of awk. Note that as i am quite the awk and bash neophyte, things are probably a bit less elegant here as they would be from a seasoned shell scripter.

MON=$(date +%m)DAY=$(date +%d)TZ=$(date +%:::z)DST=$(date +%Z | grep S)ISDST=0if [ $DST ]; then  ISDST=1fi

This script fails miserably if the character S appears in your time zone, so you'll have to localize a bit before deploying, alright?

To make an educated guess as to where we are, we can either consult an on line mapping service, your friendly GPS or a web service. The web service seed like the most lazy option, and is to be found here:

http://www.geobytes.com/IpLocator.htm?GetLocation&template=php3.txt

This will return a HTML file with some funky headers containing your assumed whereabouts. It probably won't be exact enough to deploy missiles to, but good enough for a sunset value.

Now to the parsing bits, which is where the awk knowledge of the Internets way outweighs mine.

First the location. The file name of the location output above is in the variable LOCFN.

LAT=$( awk -F'"' '/name="latitude"/ {print $4}' $LOCFN)LONG=$( awk -F'"' '/name="longitude"/ {print $4}' $LOCFN)

Then the stellar bits.

sunrise=$(awk -F'[<|>]' '/sunrise/ {print $3}' $SUNFN)sunset=$(awk -F'[<|>]' '/sunset/ {print $3}' $SUNFN)ssunrise=$(date -d $sunrise +%s)ssunset=$(date -d $sunset +%s)smorning=$(date -d 06:30 +%s)snight=$(date -d 22:30 +%s)

The first two lines extract the sunrise and sunset from the file, whose name is stored in the variable SUNFN. The next two lines parse that value into seconds since The Epoch, which makes calculations doable. The last two linea are to allow calculation when the garden lights will illuminate in the morning or go out in the evening (this bit was fixed from yesterday's edition).

Like this. No special calculations are done for weekends, which is kind of lazy, but we take care of that later.

if [[ ( ( "$ssunset" -lt "$snow" ) && ( "$snow" -lt "$snight" ) )    || ( ( "$smorning" -lt "$snow" ) && ( "$snow" -lt "$ssunrise" ) ) ]] ;then        /usr/bin/curl http://relaybox/[1-4]/HIGHelse        /usr/bin/curl http://relaybox/[1-4]/LOWfi

My Nanode-connected relay box lives at the address relaybox. Or that's what i'd like you to think when you try to hack my LAN. The relays one through four are set to high if the window of darkness is upon my garden (wooh), otherwise they're drawn low.

Finally, and this is the bit i'm not entirely proud of, an edit of the crontab:

*/2 6-10 * * 1-5 /path/to/sunset/script*/2 16-22 * * 1-5 /path/to/sunset/script

This will hit the script every two minutes in the morning and evening. If the sun rises after ten-oh-oh, the lights will stay on until sixteen-hundred. It's a bug, not a feature.

Soure available upon request.