Temperature and Humidity Sensor Network with Arduino and Ubuntu

Hardware

(Many sources, for example Amazon)

(I bought it from http://www.sparkfun.com/products/9026)

(I bought it from http://shop.moderndevice.com/products/humidity-and-temperature-sensor)

Project Enclosure:

(I bought it from http://www.sparkfun.com/products/10088)

Note: I am not being paid by any of the hardware stores to advertise their products, I am just saying that that’s where I bought the hardware.

Software Environment

Ubuntu 10.10

Firefox (version >= 3.6.16)

flot 0.6 (newer version probably works)

http://code.google.com/p/flot/

Connecting the Arduino with the PC over Ethernet

On PC:

right-click on network-manager and select “Edit connections”

or

System-menu, System->Preferences->Network Connections

Click on the Wired Tab, click on Add

then enter as in screenshots

Note that the last of the four numbers can be any number between 0 and 255, but the first three have to be the same as the first three numbers on the Arduino. In my example, I chose 192.168.1.177 as the address for the Arduino, so the first three numbers for the PC need to be 192.168.1.

This connection does not interfere with the Wifi connection, so you can be connected to the Internet while the Arduino is plugged into the only ethernet port on a laptop.

Connect the Arduino to the PC with an Ethernet cable. Modern PC’s do not even need a cross-over cable, a standard straight-through cable works.

Then, left-click on network-manager

And click on “Arduino”

And you should see the message that indicates that the Arduino and the PC are now connected.

Testing the connection with a Telnet session

telnet 192.168.1.177

Trying 192.168.1.177...

Connected to 192.168.1.177.

Escape character is '^]'.

Request a temperature value by typing ‘t’ and then the ENTER key:

t

1 Temperature: 72.13

t

2 Temperature: 72.15

t

3 Temperature: 72.17

Request a humidity value by typing ‘h’ and then the ENTER key:

h

12 Humidity: 63.76

h

13 Humidity: 63.60

h

14 Humidity: 63.57

Source Code

The project-specific software consists of four files:

File name

Platform

Language

Comments

temp_hum_eth_request.pde

Arduino

Wiring

waits for user requests on the ethernet port and returns temperature and humidity data from the sensor

telnet_to_file.sh

Ubuntu

bash script

opens a telnet session to the Arduino and stores the measurement data in a file

read_data_loop.sh

Ubuntu

bash script

converts the measurement data to JSON format for plotting by flot in a web browser

temp_hum.html

Ubuntu

Javascript

web page with live plot of sensor data

Source Code

temp_hum_eth_request.pde

/*

 * File: temp_hum_eth_request.pde

 * Author: atmelino

 * Date: 04-08-2011

 * Comment: Temperature and Humidity Sensor Network Node

 *              based on webserver example

 

 Circuit:

 * Ethernet shield attached to pins 10, 11, 12, 13

 * SHT21 sensor board attached to pins A2=GND, A3=VCC, A4=DATA, A5=CLOCK

 

 */

#include <SPI.h>

#include <Ethernet.h>

#include <Wire.h>

#include <LibHumidity.h>

// Enter a MAC address and IP address for your controller below.

// The IP address will be dependent on your local network.

// gateway and subnet are optional:

byte mac[] = {  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

byte ip[] = {  192,168,1, 177 };

// telnet defaults to port 23

Server server(23);

boolean gotAMessage = false; // whether or not you got a message from the client yet

LibHumidity humidity = LibHumidity(0);

float temp, hum;

int count;

void setup() {

  // Modern Device SHT21 board

  pinMode(16, OUTPUT);

  digitalWrite(16, LOW);  //GND pin

  pinMode(17, OUTPUT);

  digitalWrite(17, HIGH); //VCC pin

  // initialize the ethernet device

  Ethernet.begin(mac, ip);

  // start listening for clients

  server.begin();

}

void loop() {

  // wait for a new client:

  Client client = server.available();

  if (client) {

        // read the bytes incoming from the client:

        char c = client.read();

        switch(c)

        {

        case 't':

        case 'T':

          count++;

          temp=humidity.GetTemperatureF();

          server.print(count);

          server.print(" Temperature: ");

          server.print(temp);

          server.print("\n");

          break;

        case 'h':

        case 'H':

          count++;

          hum=humidity.GetHumidity();

          server.print(count);

          server.print(" Humidity: ");

          server.print(hum);

          server.print("\n");

          break;

        default:

          break;

          // if nothing else matches, do the default

          // default is optional

        }

  }

}

telnet_to_file.sh

#!/bin/bash

# File: telnet_to_file.sh

# Author: atmelino

# Date: 04-08-2011

# Comment: Read temperature and humidity from Arduino with SHT21 sensor and store in file

arduino_ip=192.168.1.177

(

while true; do

    sleep 2

    echo t

    sleep 2

    echo h

done

)|telnet $arduino_ip | tee alldata.dat

read_data_loop.sh

#!/bin/bash

# File: read_data_loop.sh

# Author: atmelino

# Date: 04-08-2011

# Comment: Extract temperature and humidity values from file and serialize in JSON format for plotting by flot with Ajax in web page

first_line=true

tz_correct=-21600000  # milliseconds for Chicage timezone

echo "reading temperature and humidity"

#create empty data files

echo '{' > temperature.dat

echo -n 'label: ' >> temperature.dat

echo  \'Temperature [Fahrenheit]\', >> temperature.dat

echo  '        data: [' >> temperature.dat

echo  ']}' >> temperature.dat

echo '{' > humidity.dat

echo -n 'label: ' >> humidity.dat

echo  \'Relative Humidity [%]\', >> humidity.dat

echo  '        data: [' >> humidity.dat

echo  ']}' >> humidity.dat

while true; do

    # Get current date and time

    var1=$(date +%s)

    var2=$((1000*$var1+$tz_correct))

    # extract measurement value

    mystring=$(tail -1 alldata.dat)

    newcount=$(echo $mystring | awk '{print $1}')

    mytype=$(echo $mystring | awk '{print $2}')

    myvalue=$(echo $mystring | awk '{print $3}')

    myline=$(echo ,[ $var2 , $myvalue ])

    if [ "$newcount" != "$oldcount" ]; then

                echo "different value";

    firstletter=$(echo ${mytype:0:1} )

    case "$firstletter" in

    T)          echo $mycount "temp"

            sed -i "/]}/i$myline" temperature.dat

                    ;;

    H)  echo $mycount "hum"

            sed -i "/]}/i$myline" humidity.dat

            ;;

    *) echo "something different"

           ;;

    esac

    fi

    oldcount=$newcount

    echo $myline

   

    sleep 1

done

temp_hum.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

  <meta name="generator"

 content="HTML Tidy for Linux/x86 (vers 25 March 2009), see www.w3.org">

  <meta http-equiv="Content-Type"

 content="text/html; charset=ISO-8859-1">

  <title>Temperature and Humidity Sensor Network</title>

  <META NAME="author" CONTENT="atmelino">

  <link href="layout.css" rel="stylesheet" type="text/css">

<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.min.js"></script><![endif]-->

<!--Note-typical pitfall: Make sure that the path to the flot library is correct!-->

  <script language="javascript" type="text/javascript"

 src="../../../lib/flot/jquery.js">

  </script>

  <script language="javascript" type="text/javascript"

 src="../../../lib/flot/jquery.flot.js">

  </script>

  <script language="javascript" type="text/javascript"

 src="../../../lib/flot/jquery.flot.navigate.js">

  </script>

  <script language="JavaScript" type="text/javascript">

//<![CDATA[

  <!--

        var x_min= (new Date("2010/12/01")).getTime();

        var x_max= (new Date("2010/12/31")).getTime();

        var options = {

            series: { lines: { show: true }, shadowSize: 0 },

            xaxis: { mode: "time", min:x_min, max:x_max, zoomRange: [0.1,  2000000000000], panRange: [0.1, 2000000000000] },

            yaxis: { zoomRange: [0.1, 100], panRange: [0.1, 100] },

            zoom: {

                interactive: true

            },

            pan: {

                interactive: true

            }

        };

        var options2 = {

                 xaxis: { mode: 'time' , min:x_min, max:x_max,},

        //                 yaxis: { min: 0 },

                 legend: { position: 'sw' } ,

            zoom: {

                interactive: true

            },

            pan: {

                interactive: true

            }

        };

        var data_temp = [];

        var data_humid = [];

        var data_tempurl = "temperature.dat";

        var data_humidurl = "humidity.dat";

        var plot_temp, plot_hum, plot;

        var time_of_last_value;

        var time_period = 10000;

        var range_names=["Last 10 seconds","Last minute","Last 10 minutes","Last hour","Last six hours","Last day","Last three days","Last week","Last two weeks","Last month","Last six months","Last year","All"];

        var range_numbers=[10,10,60,10*60,60*60,6*60*60,24*60*60,3*24*60*60,7*24*60*60,14*24*60*60,31*24*60*60,6*31*24*60*60,12*31*24*60*60];

        var range_options=[range_names, range_numbers];

  function onload_function() {

        refreshIt(); // refresh every 5 secs

        $.plot($("#placeholder1"), data_temp, options);

        for (var i=0;i<=range_names.length;i++)

        {

          AddItem(range_names[i],3);

          range_numbers[i]=1000*range_numbers[i];

        }

        loop_fetchData();

  }

  function loop_fetchData() {

            if ($("#liveData:checked").length > 0) {

            dual_plot();

            }

            setTimeout(loop_fetchData, 1000);

  }

  function refreshIt() {

            var today=new Date();

            var Y=today.getFullYear();

            var M=today.getMonth()+1;

            var D=today.getDate();  

            var h=today.getHours();

            var m=today.getMinutes();

            var s=today.getSeconds();

            // add a zero in front of numbers<10

            m=checkTime(m);

            s=checkTime(s);

            document.getElementById('date').innerHTML=M+"-"+D+"-"+Y;

            document.getElementById('time').innerHTML=h+":"+m+":"+s;

            setTimeout('refreshIt()',1000); // refresh every 5 secs

  }

            function put(){

                    number=document.forms[0].myList.selectedIndex;

                    test_function("idx:"+number+"\n"+"val:"+range_numbers[number] );

                    time_period=range_numbers[number];

            }

  function test_function()

  {

            document.getElementById('time').innerHTML="hello";

        AddItem("New Item",3);

  }

  function test_function(my_message)

  {

            document.getElementById("test01").innerHTML=my_message;

  }

  function checkTime(i)

  {

            if (i<10)

              {

                      i="0" + i;

              }

            return i;

  }

        function AddItem(Text,Value)

        {

            // Create an Option object            

            var opt = document.createElement("option");

            // Add an Option object to Drop Down/List Box

            document.getElementById("myList").options.add(opt);

            // Assign text and value to Option object

            opt.text = Text;

            opt.value = Value;

        }

  //-->

  //]]>

  </script>

</head>

<body onload=" onload_function()"

 style="background-image: url(waterfall_desktop_background-1600x1200.jpg); color: rgb(0, 0, 0); background-color: rgb(51, 102, 102);"

 link="#0000ee" vlink="#551a8b" alink="#ee0000">

<table style="text-align: left; width: 100%;" border="1" cellpadding="2"

 cellspacing="2">

  <tbody>

        <tr>

          <td style="vertical-align: top;"> <span

 style="color: rgb(255, 204, 51);">Status</span><br>

          <span style="font-family: monospace;"><span

 style="color: rgb(255, 204, 255);">Date:</span></span> <textarea

 rows="1" cols="10" id="date"></textarea><br>

          <span style="font-family: monospace; color: rgb(255, 204, 255);">Time:</span>

          <font size="-1"> <textarea rows="1" cols="10" id="time"></textarea></font><br>

          <span style="font-family: monospace; color: rgb(255, 204, 255);">IP:</span>

          <textarea rows="1" cols="12" id="IP"></textarea><br>

          <br>

          <input style="color: rgb(255, 204, 102);" id="liveData"

 checked="yes" type="checkbox"><span style="color: rgb(255, 204, 102);">Live

Data</span><br>

          <form>

            <select id="myList" onchange="put()">

            <option> Select Range </option>

            </select>

            <br>

          </form>

<span

 style="color: rgb(255, 204, 51);">Graph</span>

          <table style="text-align: left; width: 100%;" border="1"

 cellpadding="2" cellspacing="2">

            <tbody>

              <tr>

                <td style="vertical-align: top;"><br>

                </td>

                <td style="vertical-align: top; text-align: center;"> <input

 class="pan_up" value="^" type="button"></td>

                <td style="vertical-align: top;"><br>

                </td>

              </tr>

              <tr>

                <td style="vertical-align: top; text-align: center;"> <input

 class="pan_left" value="&lt;" type="button"></td>

                <td style="vertical-align: top; text-align: center;"> <input

 class="plot_reset" value="reset" type="button"></td>

                <td style="vertical-align: top; text-align: center;"> <input

 class="pan_right" value="&gt;" type="button"></td>

              </tr>

              <tr>

                <td style="vertical-align: top; text-align: center;"> <input

 class="zoom_out" value="-" type="button"></td>

                <td style="vertical-align: top; text-align: center;"> <input

 class="pan_down" value="v" type="button"></td>

                <td style="vertical-align: top; text-align: center;"> <input

 class="zoom_in" value="+" type="button"></td>

              </tr>

            </tbody>

          </table>

         <textarea rows="3" cols="20" id="test01"></textarea></p>

          <input class="fetchSeries" value="First dataset" type="button"> <span></span>

 

          </td>

          <td style="vertical-align: top;">

          <div

 style="width: 600px; height: 400px; background-color: rgb(204, 255, 255);"

 id="placeholder1"> </div>

          <br>

  </tbody>

</table>

<script id="source" language="javascript" type="text/javascript">

//<![CDATA[

  $(function () {

            var i=0;

            var placeholder1 = $("#placeholder1");

            document.getElementById('IP').innerHTML="192.169.0.177";

        $("input.zoom_out").click(function () {

            plot_temp.zoomOut();

            plot_hum.zoomOut();

        });

        $("input.zoom_in").click(function () {

            plot_temp.zoom();

            plot_hum.zoom();

        });

        $("input.pan_up").click(function () {

            plot_temp.pan({ top: -10 });

            plot_hum.pan({ top: -10 });

        });

        $("input.pan_down").click(function () {

            plot_temp.pan({ top: 10 });

            plot_hum.pan({ top: 10 });

        });

        $("input.pan_left").click(function () {

            plot_temp.pan({ left: -10 });

            plot_hum.pan({ left: -10 });

        });

        $("input.pan_right").click(function () {

            plot_temp.pan({ left: 10 });

            plot_hum.pan({ left: 10 });

        });

        $("input.fetchSeries").click(function () {

            //    dual_plot();

        });

  });

  </script><br>

<script id="source" language="javascript" type="text/javascript">

        function dual_plot()

        {

            // then fetch the data with jQuery

            function onData_tempReceived(series) {

            data_temp = $.extend(true, [], series.data);

                  }

            function onData_humidReceived(series) {

                    data_humid = $.extend(true, [], series.data);

                // and plot

                    time_of_last_value=series.data[series.data.length-1][0];

//                          test_function("time_of_last_value: "+ time_of_last_value);

                    options2.xaxis.max=time_of_last_value;

                    options2.xaxis.min=time_of_last_value-time_period;

            $.plot($("#placeholder1"),

                   [ { data: data_temp, label: "Temperature F" },

                     { data: data_humid, label: "Humidity %", yaxis: 2 }],options2 );                  

             }

           

            $.ajax({

                url: data_tempurl,

                method: 'GET',

                dataType: 'json',

                success: onData_tempReceived

            });

            $.ajax({

                url: data_humidurl,

                method: 'GET',

                dataType: 'json',

                success: onData_humidReceived

            });

    document.getElementById('test01').innerHTML="called dual_plot";

        }

  //]]>

  </script><br>

</body>

</html>

Considered Improvements

Combine the script files into one file

In order to retrieve a measurement value, a telnet session must be open to communicate with the Arduino. It would be very inefficient to open and close a new telnet session for every single value; it is more efficient to open a telnet session, to retrieve measurement values in a loop, and to leave the telnet session open until the program closes. The down side is that it not possible to call a function that is inside of a loop. This is why I initially wrote two script files: one with the telnet loop, and one for the JSON format conversion.

Obviously, it would be nice to call a function that retrieves a measurement value from the Arduino and have just one script file. One possible way to deal with the telnet session issue is, instead of opening and closing a session for every single value, to inquire whether a session is open, and if not, open one. This should have the added advantage that if a session was unintentionally closed, it would be re-opened.

MySQL Database

There are several advantages to storing the measurement data in a database. First, when a LAMP software stack is installed (Linux - Apache - MySQL - PHP), the web page script can issue a JSON request and the database will generate the data with built-in calls, there is no longer the need to manually assemble the JSON file. Second, having the data in a database leads to a somewhat standardized way to access the data, as opposed to a data file with an arbirtrary format. Third, it would be fun to learn LAMP, to design a relational database, and to play with other visualization software that uses databases.

Distributed Architecture

At this time, the design relies on a server that polls the data from the Arduino and stores them on the server storage. The web page client then retrieves the data from the server and plots them. Alternatively, a client may be able to connect to the Arduino directly and retrieve the data from the Arduino. In this scenario, the web page HTML code and Flot Javascript library files could still be stored on a PC, and the measurement data could be stored on the ethernet shield’s SD card. There are Ajax libraries for the Arduino that could hopefully send data in JSON format. A scenario that goes even further is where the web page itself is generated on the Arduino, so a client’s HTML request points directly to the Arduino’s IP address. But how do you store the Flot library on an Arduino?

Source Code Management

Since this is beginning to look like an ongoing evolving project, with multiple development branches, so I’ll probably start using a source code management tool (think version control)

I have used CVS and Subversion in the past, but git seems to be the new evolving standard. Linus Torvalds thinks git is not only better than CVS and subversion, but CVS and subversion are .. well, see for yourself what he has to say :-)

http://www.youtube.com/watch?v=4XpnKHJAok8