AmbiLamp: Lamp Code
Running on Boot

This last lesson for the Raspberry Pi uses crontab. Crontab is a file where you can tell the Pi to take certain actions periodically. People often use it if they need something to happen every time the Pi boots up, or to make backups once a week. We have 3 things we want crontab to manage for us:

On boot, run test_hardware.py so we can see the IP address and instantaneous readings
Every 30 minutes, take a data reading
On boot, run use_rgb.py so we listen for changes to the color

Start by navigating to the folder where your two scripts from the last two lessons live. For this lesson, I am going to use the names get_data.py and use_rgb.py as the two files - in all instructions, make sure to use your own file names.

In that directory, execute

$ pwd
which will print out the location of these files. Take note of this path - we will need it in crontab.

Now execute

$ crontab -e
which opens a file. Take a moment to read the comments that the files comes with because they describe how to use it, and then add a reboot line at the bottom, like so:

Do not forget the ampersand (&) at the end of the line! It tells the pi to run the script in the background. Without it, the entire device will wait for the script to exit, and never finish booting.

See above. We just really don't want you to miss it. You'll be sad.

Use the path you found with pwd. Save the file and reboot the Pi (sudo reboot now). After a minute, you'll see the script run! And conveniently, it shows the IP address, so you can log back into the Pi without any pesky cables.

Run get_data.py

Open crontab again, and add the following two lines. Based on the comments in crontab, do you understand why we need two lines to run get_data.py? The first line reads as "when the minute equals 0 for any hour, and day, any month, etc.." So, the first line will be run at 00:00, 01:00, 02:00, etc..

Run use_rgb.py

The LED Problem

Use RGB poses a little bit of a problem. Eternal while loops (called polling) are dangerous and bad style because they use up a lot of resources, and over a long time, there is a risk of the script crashing. And then we lose control over the lamp! However, the smallest interval crontab gives us control over is one minute, and we want the lamp to update far faster than that.

We could rewrite the script to run for a minute and then exit, so we could run it every minute and rest assured that if one instance crashes, it'll be recreated the next minute, but since requests.get() takes an uncertain amount of time, we can never be sure that the script will finish running in time. If it takes too long and doesn't finish executing before crontab runs a new instance, we'll have two scripts trying to write to the same hardware pins - this situation is known as a race condition, and is even worse than polling. There is an option to timeout a process, so we could force the process to end prematurely to avoid race conditions, but there is also some setup time, where the first requests.get() call takes several seconds, and every request after takes about one second. Losing 10 seconds every minute would be a bad experience for the user.

The LED Solution

Given all these tradeoffs, we are going to compromise. We will keep our while loop, which checks for color updates as fast as it can, and we will start running it when the Pi boots up. However, in case it crashes, every night at 03:00 (3am) we will start the script again, with a timeout of 86399 seconds, which is one second less than 24 hours. That way, we know that the process will end one second before the new process begins, avoiding race conditions, and we only lose the setup time once a day, when a user is unlikely to be using the lamp anyway.

The remaining problem is that we don't know how long to run the instance of use_rgb.py that we start on boot for. We can't just wait for 3am. But at 3am a new instance will start running, so we need to kill the one started on boot before that. To solve this, we actually won't start use_rgb.py directly from crontab. Instead, crontab will run a bash script that determines how many seconds there are until the next 3am, and start use_rgb.py with a timeout of that many seconds, minus one. We'll make test_hardware.py follow this cycle as well, so that we don't have to worry about our display crashing either.

So, with no further ado: the code for this plan. The new lines for crontab are shown below - note that we changed reboot to call the boot_rgb.sh bash script, instead of starting test_hardware.py directly. We also added two lines at the bottom, which refresh the instances of both scripts at 3am every morning.

Now, in the command line, open a new file (sudo vim boot_rgb.sh) and paste in this code.

#!/bin/bash

# get current time
CURRENT=$(TZ='America/Los_Angeles' date "+%F %H:%M:%S")
CURRENT_HOUR=$(date +%H)

# get the next 3am
if [ $CURRENT_HOUR -lt 3 ] # it is already the same day
then
TARGET=$(TZ='America/Los_Angeles' date  "+%F 03:00:00")
else
TARGET=$(TZ='America/Los_Angeles' date -d "+1 days" "+%F 03:00:00")
fi

# get number of seconds between them
DIFF="$(($(date -d "$TARGET" '+%s') - $(date -d "$CURRENT" '+%s')))"

# call use_rgb.py and test_hardware.py with a timeout of that many seconds
timeout -s 2 $(($DIFF-1)) sudo python /home/pib/iot_ambilamp/code/test_hardware.py &
timeout -s 2 $(($DIFF-1)) sudo python /home/pib/iot_ambilamp/code/use_rgb.py &

Remember to change the file paths to match yours

At long last, go ahead and reboot the Pi. You should see the screen turn on, be able to control the LED from the web app, and see a new data entry every half hour, all without having to log into the Pi. Crontab has automated it for us.