U bekijkt nu de Engelstalige versie van onze website. Klik hier om de site in het Nederlands te bekijken.

Temperature monitoring with the Raspberry Pi

Introduction

For a catering industry related project, I wanted to create a simple web page which shows a log of the temperatures of a set of refrigerators and freezers. So I needed to create a small system that reads some temperature sensors and sends this to a database for further processing. I had a Raspberry Pi lying around which I didn't actually use, so I started looking for tutorials about temperature monitoring with the Pi.

Wiring

The tutorial which gave the most information about the Raspberry Pi in combination with temperature monitoring, was the tutorial of Matthew Kirk. From this tutorial I learned that the best one-wire temperature sensor would be the DS18B20. It could be directly attached to GPIO4 port. The only thing I had to add was a 4K7 Ohm resistor between the GPIO4 port and the 3.3V line.

The scheme is depicted in the following image:
DS18B20 Raspberry Pi scheme
Because I wanted to read out more than one sensor, I hoped that I could use the other GPIO ports on the Raspberry Pi in exact the same way as we used the GPIO4 port. But, the current Wheezy Raspberry Pi kernel only supports the One-wire DS18B20 temperature sensor on the GPIO4 port. So, I looked like one sensor would be the maximum per Raspberry Pi. Fortunately, one of the most important features of the DS18B20 was not mentioned earlier (or I didn't find it yet): The DS18B20 is one of the few one-wire temperature sensors that could be connected in parallel!. So this should mean that we could add as many sensors as we like and we could reuse parts of the cable running between the sensors and the raspberry. Also, when you use multiple DS18B20 sensors, you still need to use only one 4K7 Ohm resistor.

So the wiring is extremely simple, it will look like this (if you also have the waterproof version of the sensors):
Parallel wiring of the DS18B20

GPIO and Therm kernel modules

From the tutorial mentioned earlier, we know that we could activate the kernel module for the GPIO pins on the Raspberry Pi and the thermometer module by executing the commands:
sudo modprobe w1-gpio
sudo modprobe w1-therm
We do not want to do that manually every time the Raspberry reboots, so we want to enable these modules on every boot. This can be done by editing the following lines to the file /etc/modules:
w1-gpio
w1-therm

Manual read-outs

I have multiple temperature sensors, which will now show up with different ID's in /sys/bus/w1/devices/. We can get a temperature value by executing:
cat /sys/bus/w1/devices/<ID>/w1_slave

Python read-outs

In Python, the devices can be opened as a file. Based on the tutorial Python code, I created a loop which will read out a specified set of sensors:
import time
 
sensorids = ["28-000004eb4e02", "28-000004eb6805"]
avgtemperatures = []
for sensor in range(len(sensorids)):
        temperatures = []
        for polltime in range(0,5):
                tfile = open("/sys/bus/w1/devices/"+ sensorids[sensor] +"/w1_slave")
                # Read all of the text in the file.
                text = tfile.read()
                # Close the file now that the text has been read.
                tfile.close()
                # Split the text with new lines (\n) and select the second line.
                secondline = text.split("\n")[1]
                # Split the line into words, referring to the spaces, and select the 10th word (counting from 0).
                temperaturedata = secondline.split(" ")[9]
                # The first two characters are "t=", so get rid of those and convert the temperature from a string to a number.
                temperature = float(temperaturedata[2:])
                # Put the decimal point in the right place and display it.
                temperatures.append(temperature / 1000)
                time.sleep(1)
        temperatures = sorted(temperatures)
        del temperatures[6]
        del temperatures[0]
        avgtemperatures.append(sum(temperatures) / float(len(temperatures)))
Update 1-7-2013: As someone mentioned in the comments, it is not needed to perform dropping and averaging to discard faulty temperature values. In the first line of the temperature sensor response, there will be indicated if the CRC checksum is valid or not (so if the value could be trusted or not). So I altered the code such that it will get a temperature value until the CRC checksum is correct. Note that I still perform averaging of three measurements, just to get a more stable value.
import time
 
sensorids = ["28-000004eb4e02", "28-000004eb6805"]
avgtemperatures = []
for sensor in range(len(sensorids)):
	temperatures = []
	for polltime in range(0,3):
			text = '';
			while text.split("\n")[0].find("YES") == -1:
					tfile = open("/sys/bus/w1/devices/"+ sensorids[sensor] +"/w1_slave")
					text = tfile.read()
					tfile.close()
					time.sleep(1)
 
			secondline = text.split("\n")[1]
			temperaturedata = secondline.split(" ")[9]
			temperature = float(temperaturedata[2:])
			temperatures.append(temperature / 1000)
 
	avgtemperatures.append(sum(temperatures) / float(len(temperatures)))
And now the temperatures are in the avgtemperatures list. Note that I poll the sensors 5 times, with a sleep of one second between the polling. This is because I noticed that the sensors do not give stable values at all times. This is also the case when I opened the device with cat. Every once in a while the temperature values of the sensor are parsed into a very large negative or positive value. To overcome this problem, I added some outlier filtering by getting 5 measurements, sorting them from low to high, dropping the lowest and highest value and than applying averaging on the remaining values. This reduces the effect of the reading errors, which would give an unrealistic drop in the temperature graphs in the end.

HTTP posts to web server

From this point on, you could create a MySQL connection in the same Python script and adding the values to the temperature monitoring database. Unfortunately, my website hosting provider does not support database connections from outside their domain. So I had to add some code which would send the values to the web server by means of a HTTP request. To make sure nobody is able to mess around with the values, I also added simple password authentication.
# Start a session because the server needs to be able to link the nonce request and the actual data post request.
session = requests.Session()
 
# Getting a fresh nonce which we will use in the authentication step.
nonce = session.get(url='url_to_server_side_script?step=nonce').text
 
# Hashing the nonce, the password and the temperature values (to provide some integrity).
response = hashlib.sha256(nonce + 'PASSWORD' + str(avgtemperatures[0]) + str(avgtemperatures[1])).hexdigest()
 
# Post data of the two temperature values and the authentication response.
post_data = {'response':response, 'temp1':avgtemperatures[0], 'temp2': avgtemperatures[1]}
 
post_request = session.post(url='url_to_server_side_script', data=post_data)
Note that from now on, you need to import requests and hashlib. It could be required that you install the requests module for Python.

Adding a cronjob

To run this python script every few minutes, you will need to add a crontab entry (running crontab -e). I'm polling the sensors every two minutes with:
# m h  dom mon dow   command
*/2 * * * * /usr/bin/python /path/to/python_temperature_polling_script.py

Server side code

And on the other side, we need to provide a nonce, check the authentication and add the value to a database table.
<?php
 
define("PASSWORD","password");
 
session_start();
 
if(isset($_GET['step']) && $_GET['step'] == 'nonce') {
	getNonce();
} else if (isset($_POST['response']) && isset($_POST['temp1']) && isset($_POST['temp2'])) {
	checkAuthenticationResponce();
	processEntry();
}
 
function checkAuthenticationResponce() {
	if(!isset($_SESSION['tempNonce']) || hash('sha256', $_SESSION['tempNonce'] . PASSWORD . $_POST['temp1'] . $_POST['temp2']) != $_POST['response']) {
		header("HTTP/1.0 401 Authorization Required");
		exit;
	} else {
		unset($_SESSION['tempNonce']);
	}
}
 
function getNonce() {
	$_SESSION['tempNonce'] = hash('sha256', 'some secret nonsense to make sure nobody can predict this nonce' . time());
	echo $_SESSION['tempNonce'];
}
 
function processEntry() {
	$mysqli = new mysqli("database_connection_information");
 
	/* check connection */
	if ($mysqli->connect_errno) {
		header("HTTP/1.0 500 Internal Server Error");
		exit();
	}
 
	/* Create table doesn't return a resultset */
	$stmt = $mysqli->prepare("INSERT INTO temps (temp1, temp2) VALUES(?,?)");
	$stmt->bind_param('dd', floatval($_POST['temp1']), floatval($_POST['temp2']));
 
	if ($stmt->execute() === true) {
		echo "added";
	} else {
		header("HTTP/1.0 500 Internal Server Error");
	}
 
	$mysqli->close();
}
 
?>
And then, the values are added to the database. As you can see in the last steps, I was a bit lazy and did not make the code that much extensible. I could have altered the code in such a way that the python script adds all the values in the avgtemperatures list to the post data such that if you want to add another sensor, you only need to add the sensor id to the list of sensor ID's. Also, the server side database could be altered such that the sensor id is stored alongside the temperature value. But for now, it works.

Displaying the values

Now the values are stored in the database, you could start creating the dashboards which will show you the temperature statistics. To get you up to speed: at this moment, I use the CSS3 Thermometer style that Daniel Stancu created with a temperature graph using the Amchart Library
Temperature dash
As this is a personal project, IADA does not guarantee that this code will work as intended in any circumstance. We do not accept liability for damage resulting from following the instructions outlined in this article and/or using the provided code. We do not offer any professional support on the information or the code provided in this article. Saus - Quick and easy time tracking
Attachments: 

Add new comment

Comments

How can I assign a "name" to each sensor?

I see right in the beginning you have each sensor's ID listed, but don't really use the sensorid's, other than reading their files in the /sys folder.

How would I be able to distinguish each sensor's "name" easily?

​Great tutorial! You can use easily DHT11 or DHT22 sensor with only two (2) wires. Here is a useful link:
https://www.facebook.com/spectrasrl/photos/p.635952796529390/635952796529390
Here is one tech video with DS18S20 and DHT11 sensors designing an industrial temperature monitoring system, Raspberry Pi based:
https://youtu.be/Knis6KReK1A

Hi
Great project!
Can you share how you did the filtering to drop the high and low temperatures before saving them?
Thanks

Just want to say THANKS!
I found DB details on other web but this article really help.
It works!

Hi All,
I got a question. I downloaded the attached files and put them on my raspberry.
I kinda hoped it was plug and play but ofcourse it isnt :D but that is no problem.
Is there any one willing to help me out what i am missing ? I think I have to make a database on my raspberry ? Is there more to do ?

I hope some one can find some time to point me to the missing stuff?

Hi! Excellent article. Thank you. I was reading it with great pleasure. I wanted to make the same and hoped that code will work as intended in my circumstance. But no. Something is wrong and I can't understand what exactly?(
Maybe it's the problem with sensors? I don't know. I would like to buy all equipment as you use and want to ask, where did you get sensor DS18B20, I looked through a lot of website like amazon, I even found this link http://hardware.nl/klemko but unsuccesful(
I will be very thankful for your fast reply

Hello,
Great tutorial! Thank you!
I'm new at RPi and programing in Python and I want to use your code to monitor 2 sensors. However, I'm stuck with error "session = Session()
NameError: name 'Session' is not defined" in poolSensors.py
What I'm tring to do is to have apache server and mysql server on RPi locally. Can someone point me how to edit pollSensors.py to be able to write temperatures in database.
I think I also need to edit saveTemp.php?

Thank you in advance
Aleksandar

I keep getting "Internal Server Error" my database is fine and i can connect to it using:
<?php
$link = mysql_connect('thepcagecom.ipagemysql.com', 'test1', '*password*');
if (!$link) {
die('Could not connect: ' . mysql_error());
}
echo 'Connected successfully';
mysql_select_db(qutweather);
?>

however when i try putting this information into the acctual index.php file it comes up with an error. Can someone please help? Im guessing its because i have no tables in the database?

Hi,
I am using 3 DS18B20 temperature sensors. Even after I unplug on of the temperature sensors, I get output as -0.6C. I found that directory of the removed sensor is deleted from sys/bus/w1/devices after 40 to 50 seconds.
Can you please help me to exception handel this problem, by removing the directory immediatly or by ignoring the false values.

Regards

Christiaan's picture

Hi Rao,
In my code, I set the ids of the sensors hardcoded. So when you unplug the sensor, the python script will still look for the disconnected sensor. It is strange that you still get a reading, I would expect an exception or something. But because every device in linux is considered a file, I think you could test if the directory exists before polling the sensor. See http://stackoverflow.com/questions/8933237/how-to-find-if-directory-exis...

Pages