My Raspberry Pi arrived a few weeks ago, and has been a great way to pass these cold snowy winter days. I have a number of projects in mind, but one was to interface with a temperature sensor and serve the results to a web page. I had a couple of the DS18B20 temperature sensors lying around from some undergrad EE project from long ago, so why not put them to use. There’s a great tutorial by Adafruit on how to set up the sensor itself and use a simple python script to print the temperature to the terminal. The rest of this post assumes that the sensor has been set up as in the guide, and the python script prints valid numbers to the terminal. Basically, the data pin of the sensor is connected to the Pi’s GPIO #4 with a 4.7K pull-up resistor, and the other two pins are power and ground.

RPi Thermometer

Adafruit’s python script is nice, but we want to add a graphical plot of temperature vs. time, embedded in a web page. Since python is the go-to language for the Pi, and it’s what I’m most used to for these things, we’ll use it. Disclaimer: I’m far from a linux or web-programming expert, so there’s likely better or more appropriate ways of doing things, but this works for me.

Install modules

Start by installing some modules. The webserver I used is lighttpd, using cgi to talk to python. The graphing is done by matplotlib.

sudo apt-get install lighttpd
sudo apt-get install python-matplotlib

(Optional) Mount external drive to store website files

It can be useful to host the website files on an external drive and not the SD card. Media files can take up a lot of space, so an external hard drive will provide more space, plus should lengthen the life of the boot SD card used by the Pi. The drive should not be formatted using FAT32, as file permissions aren’t understood by this filesystem. I used ext3, but others are possible too. Plug in the drive, then we need to mount it. First, we need to determine where the drive lives in the system. The blkid command will list the drives and their designation. Find your drive in the list and look at the first column to see where it is in /dev, mine is /dev/sda1. We also need to make a folder for the mounted drive to live on, you can choose any name you want, but I did:

sudo mkdir /mnt/usb0

Then to automatically mount the drive on boot-up, edit the /etc/fstab file:

sudo nano /etc/fstab

Add the following lines and save the file:

/dev/sda1       /mnt/usb0       ext3    users,rw,nofail,exec   0       0
/mnt/usb0/www   /var/www        bind    none

The first line will mount the drive to the folder /mnt/usb0. It specifies the ext3 format, and some other parameters. The rw and exec are important to allow the webserver to read, write, and execute files on the drive. The second line will bind the www folder on the external drive over the /var/www folder on the SD card. Then we won’t have to change any config files for the webserver, and if the external drive is not present, the server will just use the SD card’s /var/www. Of course, we need the www folder on the external drive to put our web site into:

mkdir /mnt/usb0/www

Reboot the pi to make sure the changes took effect:

sudo reboot

Set up the temperature sensor

Now to the fun stuff. We need a script to record the temperature to a file periodically. We’ll modify the Adafruit script to print just a single temperature value, along with a timestamp, and then exit. Then create a cron job to run the script periodically and dump the output into a file. Then the web server can load the data from the file when it serves up the page. I suppose a more web-proper way of doing this would involve using a database, but raw text files are easier for now. First the kernel modules used by the temperature sensor must be initiated as root, but we don’t want to run our server as root, so let’s load them at boot-up, then we can take them out of the python code.

sudo nano /etc/modules

Add the lines below and save the file:

w1-gpio
w1-therm

Now for the python code. This is mostly identical to the Adafruit tutorial’s code.

#!/usr/bin/python

import glob
import time

# These commands can be done at boot in /etc/modules
#os.system('modprobe w1-gpio')
#os.system('modprobe w1-therm')

base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'

def read_temp_raw():
    f = open(device_file, 'r')
    lines = f.readlines()
    f.close()
    return lines

def read_temp():
    lines = read_temp_raw()
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.2)
        lines = read_temp_raw()
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c = float(temp_string) / 1000.0
        temp_f = temp_c * 9.0 / 5.0 + 32.0
        return temp_c, temp_f

if __name__ == "__main__":
    print '%f, %f'%(time.time(), read_temp()[1])

The main differences are removal of the os commands, and the main program only printing one line before exiting. Save the script as /var/www/temp.py (using sudo) and give it executable permissions by running

chmod +x temp.py

Test it out:

/var/www/temp.py > /var/www/temp.dat

And you should get a temp.dat file with the current time and temperature in it. Give the data file to the www-data user so this can be done from the server:

chown www-data:www-data /var/www/temp.dat

Now, we want this script to run periodically and send the output into a file. We’ll use a cron job for that, but let it run under the www-data user.

sudo crontab -u www-data -e

Add the following line, which tells the Pi to run the /var/www/temp.py script every 1 minute, with the output appended to /var/www/temp.dat. Of course, you can specify any interval you want; 1-minute may be excessive for some purposes, but makes it easier to ensure things are working. I’d suggest changing it to */15 or something more reasonable later.

*/1 * * * * /var/www/temp.py >> /var/www/temp.dat

Set up the web server

Next let’s set up our web server. We’ll use lighttpd configured to run python through cgi. We need to enable cgi:

sudo lighty-enable-mod cgi

Then edit the cgi configuration file:

sudo nano /etc/lighttpd/conf-enabled/10-cgi.conf

We need to tell the configuration how to run python files, so it should look something like this. It enables the cgi module and assigns .py files to /usr/bin/python, but only if the python file lives within the cgi-bin directory.

server.modules += ( "mod_cgi" )
$HTTP["url"] =~ "^/cgi-bin/" {
 cgi.assign = ( ".py" > "/usr/bin/python" )
}

Note in this file you can also assign perl or php if needed for other purposes. Save the file and restart the server with:

sudo service lighttpd force-reload

Write the web page code

Ok, now it’s time to code the actual web page! The site will consist of a static index.html file which can contain text and will have the dynamic temperature graph embedded as an image. So in the /var/www folder, create an index.html file with the following contents:

<html>
<head>
<title>Temperature Graph</title>
<body>
<p>Temperature of the bedroom:<br>
<img src="cgi-bin/temp_graph.py"></p>
</body>
</html>

Finally, we need the actual code that generates the image. Make a cgi-bin directory within the /var/www directory:

mkdir /var/www/cgi-bin

and create a temp_graph.py file there, with the following contents.

#!/usr/bin/env python

import cgi
#import cgitb; cgitb.enable() # Enable for debug mode
import io
import numpy as np

import matplotlib
matplotlib.use('Agg')
matplotlib.rcParams['timezone'] = 'US/Eastern'  # Replace with your favorite time zone
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter

# Read the data file
data = np.genfromtxt( '../temp.dat', delimiter=',' )
dates = matplotlib.dates.epoch2num(data[:,0])
tempdata = data[:,1]

# Set up the plot
fig, ax = plt.subplots(figsize=(6,5))
ax.plot_date( dates, tempdata, ls='-', color='red' )
ax.xaxis.set_major_formatter( DateFormatter( '%m/%d/%y %H:%M' ) )

# Read the number of hours argument and set xlim
arg = cgi.FieldStorage()
try:
    h = int( arg.getvalue('hrs', '-1') )
except:
    h = -1
if h > 0:
    ax.set_xlim( matplotlib.dates.epoch2num(data[-1,0]-h*3600), ax.get_xlim()[1] )

# Finish plot
ax.set_ylabel('Temperature F')
for label in ax.get_xticklabels():
    label.set_rotation(60)
plt.tight_layout()

# Save the image to buffer
buf = io.BytesIO()
fig.savefig(buf, format='png')
out = buf.getvalue()
buf.close()
print 'Content-Type: image/png\n'
print out

In a nutshell, the script loads the data file, plots up the data, checks the input arguments for the number of hours to plot, and spits out a png image. Save this file, give it executable permissions just like the first python file (chmod +x temp_graph.py), and everything is set! Navigate a browser to your local ip, in my case it was http://192.168.2.10. You should get a page with the title and the generated image.

Temp vs Time

On this cold day (20°F outside), you can see my furnace kicks on about every 10-15 minutes (adding a bit to my gas bill every time), and has hysteresis of about one degree! Test the time specification by adding the “hrs” parameter: http://192.168.2.10/?hrs=2 to show the past 2 hours of data. Without a parameter, it shows the entire data file. You can also serve up the image only by navigating directly to http://192.168.2.10/cgi-bin/temp_graph.py.

Here’s the output after logging for about 24 hours.

Temp vs Time 2

Data Analysis

Of course, I’m not content to just look at the data, I have to analyze it. I already mentioned the approximately 15-minute cycles of my furnace, and the temperature hysteresis of about a degree. What about the longer-term trends? The temperature starts about 69, when my programmable thermostat was set for 68, but the Pi and thermometer are in a back bedroom which tends to feel warmer than the living room where the thermostat is. At 15:00, the thermostat drops to its regular weekend schedule of 66 during the day. The big drop at 19:00 was when the gas fireplace was turned on, making it nice and toasty in the living room where the thermostat is, yet letting the bedrooms get really cold! Then about 20:00, the fireplace was turned off, and the bedrooms were really cold so the thermostat was manually bumped to about 69. It stayed there until 6:00 the next morning when the normal schedule took over again. So how much time was the furnace on? Using python/numpy offline, I took the first derivative to find where the temperature was rising or falling. A portion of the data is shown here.

dT/dt

If we assume the furnace to be on when above zero, and off when below, we can make the data binary.

Furnace on

Then we can use numpy.count_nonzero() and get some estimate of how many minutes the furnace was on versus how long it was off. In this case, it was on about 28% of the time! That will make for a high gas bill this month. Of course, the nighttime low temperature outside was 7°F, so it did have to work hard. I should repeat this measurement in a month or two and hopefully get a lower number.

One final step. What’s the on-rate during different cycles? At the end of the data, when the temperature is being maintained at 69, the on-rate is about 25%. Before that, when being maintained around 70-71, the on-rate is around 33%, but it was also much colder outside during these overnight hours. During the massive warm-up from 20:00 to 0:00, the rate is near 40%! Interesting. Tomorrow the thermostat goes in to its weekday schedule which will be much cooler than the weekend schedule, so there will be more data to look at.