Home‎ > ‎

Arduino Temperature and Humidity Logger

Arduinos are used by many Hams for APRS Weather Station applications and other station automation apps. I had no experience with the Arduino so I jumped at the chance to learn something new when I had a real world application.

I recently moved to a house built in 1996 and it is in dire need of updates and maintenance due to neglect. One of the major problems is excessive moisture in the crawlspace. One way to solve this problem is to encapsulate the crawlspace and use a dehumidifier to regulate the humidity. I did some preliminary encapsulation and needed a way to monitor the temperature and humidity so I could get a baseline and know the extent of the problem.

The goal was to create a method of logging temperature and humidity every 10 minutes at multiple locations. I decided to monitor the attic in addition to the crawlspace. I will need hardware, a database, and some way to view and store daily graphs.

The hardware for this project is an Arduino Uno with an Ethernet Shield and interfaces to the temperature and humidity sensors. The database is MySQL running on a Debian Linux distribution. To view the graphs I use Apache and PHP running on the same machine.

I will provide the Arduino code, circuit board layout for the temperature and humidity sensors, the initial database configuration, and the PHP code to generate and display the daily graphs.

You should have, or be willing to develop, the following skills if you want to successfully complete the project:

  • Printed Circuit Board etching and assembly
  • Arduino programming
  • Experience with Linux, MySQL, Apache and PHP

Arduino Hardware

The hardware consists of a stock Arduino Uno, an Ethernet Shield and a custom interface to two DHT22 sensors. You will also need some headers soldered on the interface board to plug into the Ethernet Shield.


The Arduino Uno is on the bottom, the Ethernet Shield is in the middle and the interface board is on top. The sensor interface board will accommodate four sensors, but I am currently only using two.


Click on the image above to view it full size. The PCB image is 600 dpi and if printed properly on Mylar, you can create the PC Boards necessary for this project. Included in the image is the Arduino interface board, nine temperature / humidity sensor boards (only two are used in this project) and two gas sensor boards (not used in this project). Feel free to edit out my call sign or cut and paste the individual boards to suit your needs.

The interface board and the sensor boards use vertical RJ-45 connectors and the cable connecting them is a standard ethernet cable.

The Arduino Code (TempHumLogger.ino):


#include <SPI.h>
#include <Ethernet.h>

#include "DHT.h"

#define DHTPIN_A   2
#define DHTPIN_B   3
#define DHTTYPE DHT22

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,254,98);
byte server[] = { 192, 168, 254, 104 };

// Initialize the Ethernet server library
// with the IP address and port you want to use 
// (port 80 is default for HTTP):

EthernetClient client;
//DHT dht(DHTPIN, DHTTYPE);
DHT dht_A(DHTPIN_A, DHTTYPE);
DHT dht_B(DHTPIN_B, DHTTYPE);

void setup() {
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }


  // start DHT
  dht_A.begin();
  dht_B.begin();
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);

}

void loop()
{
 
  if (client.connect(server, 80))
  {
     

    int h1 = dht_A.readHumidity() + .5;
    int t1 = dht_A.readTemperature() + .5;
    int h2 = dht_B.readHumidity() + .5;
    int t2 = dht_B.readTemperature() + .5;
    if (isnan(t1) || isnan(h1) || isnan(t2) || isnan(h2)) {
            Serial.println("Failed to read from DHT");
          } else {


    client.print( "GET /add.php?");
    client.print("temp1=");
    client.print(((t1*9)/5) + 32);
    client.print("&&");
    client.print("humi1=");
    client.print( h1 );
    client.print("&&");
    client.print("temp2=");
    client.print(((t2*9)/5) + 32);
    client.print("&&");
    client.print("humi2=");
    client.print( h2 );

    client.println( " HTTP/1.1");
    client.println( "Host: 192.168.135.104" );
    client.println( "Content-Type: application/x-www-form-urlencoded" );
    client.println( "Connection: close" );
    client.println();
    client.println();
    client.stop();
  }
    Serial.print("  temp1=");
    Serial.print(((t1*9)/5) + 32);
    Serial.print("  humi1=");
    Serial.print( h1 );
    Serial.print("  temp2=");
    Serial.print(((t2*9)/5) + 32);
    Serial.print("  humi2=");
    Serial.println( h2 );
  delay( 600000 );
}
}


In the code you will see that I'm using pins 2 and 3 on the Arduino Uno. 
These pins are not used by the Ethernet Shield.
#define DHTPIN_A   2
#define DHTPIN_B   3

You will need to change the the IP Address of the device and the IP Address of the web server the board will be talking to in the following lines:

byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,254,98);
byte server[] = { 192, 168, 254, 104 };

Optionally change the MAC address, especially if the Arduino is connected to the Internet.
The Arduino connects to a web server and talks to a PHP script called add.php.

Note the line at the end of the file: delay( 600000 );
This delay will limit logging to once every 10 minutes. Change this number for faster or slower logging.

In addition to TempHumLogger.ino created from the text above, you will need the DHT and Ethernet libraries available on GITHUB (see http://arduino.cc/),

The Database

I'm running a Debian Linux server running as a Virtual Machine on my home Windows 7 computer. You will need Apache, PHP and MySQL running on the same Linux machine.

On the MySQL server, I created a database called "Temp_Humi_Gas" with a single table called "logtable". Here are the columns defined for the table:

Column Type Null Default
timestamp timestamp No  CURRENT_TIMESTAMP 
temp1 varchar(255) No   
humi1 varchar(255) No   
temp2 varchar(255) No   
humi2 varchar(255) No   

Indexes: 

Keyname Type Unique Packed Column Cardinality Collation Null Comment
timestamp BTREE No No timestamp 535 A

At the root web directory on your web server (on Debian it's /var/www), you will need two PHP files;

add.php

<?php
   include("conec.php");
   $link=Conection();
$Sql="insert into logtable (temp1,humi1,temp2,humi2)  values ('".$_GET["temp1"]."', '".$_GET["humi1"]."', '".$_GET["temp2"]."', '".$_GET["humi2"]."')";     
   mysql_query($Sql,$link);
   header("Location: insertareg.php");
?>

conec.php

<?php
function Conection(){
   if (!($link=mysql_connect("localhost","db_user_name","db_user_password")))  {
      exit();
   }
   if (!mysql_select_db("Temp_Humi_Gas",$link)){
      exit();
   }
   return $link;
}
?>

Change db_user_name and db_user_password to the MySQL user and password with sufficient privileges to write to your database.
Note: When you copy the text for the PHP files, make sure there is not a blank line or spaces after the ?> line. You may get some PHP errors if you do.

At this point your hardware is logging temperature and humidity data for two sensors. The following section describes my method for viewing the data on a web browser and saving graphs every night at midnight.

The Web Server

I decided to create a directory for my graph display script and support files called "automation". There are several graphing libraries available and I chose "phpgraphlib". At a minimum you will need to download phpgraphlib.php from GITHUB. The direct link is https://github.com/elliottb/phpgraphlib. If you don't see a download hyperlink, you will need to log onto GITHUB with a free account.

I wrote a PHP script called environment.php which requires a cascading style sheet called environment.css. The file environment.php passes arguments to another script called graph.php which uses phpgraphlib.php.

environment.css

<style>
body {
        margin:0px;
        padding:0px;
        font-family:verdana, arial, helvetica, sans-serif;
        color:#333;
        background-color:rgb(183,183,183);
        }
h1 {
        margin:0px 0px 15px 0px;
        padding:0px;
        font-size:28px;
        line-height:28px;
        font-weight:900;
        color:#ccc;
        }
p {
        font:11px/20px verdana, arial, helvetica, sans-serif;
        margin:0px 0px 16px 0px;
        padding:0px;
        }
#Content>p {margin:0px;}
#Content>p+p {text-indent:30px;}

a {
        color:#09c;
        font-size:11px;
        text-decoration:none;
        font-weight:600;
        font-family:verdana, arial, helvetica, sans-serif;
        }
a:link {color:#09c;}
a:visited {color:#07a;}
a:hover {background-color:#eee;}

#Header {
        margin:0px 0px 10px 0px;
        padding:17px 0px 0px 20px;
        /* For IE5/Win's benefit height = [correct height] + [top padding] + [top and bottom border widths] */
        height:33px; /* 14px + 17px + 2px = 33px */
        border-style:solid;
        border-color:black;
        border-width:1px 0px; /* top and bottom borders: 1px; left and right borders: 0px */
        line-height:11px;
        background-color:#eee;

/* Here is the ugly brilliant hack that protects IE5/Win from its own stupidity.
Thanks to Tantek Celik for the hack and to Eric Costello for publicizing it.
IE5/Win incorrectly parses the "\"}"" value, prematurely closing the style
declaration. The incorrect IE5/Win value is above, while the correct value is
below. See http://glish.com/css/hacks.asp for details. */
        voice-family: "\"}\"";
        voice-family:inherit;
        height:14px; /* the correct height */
        }
/* I've heard this called the "be nice to Opera 5" rule. Basically, it feeds correct
length values to user agents that exhibit the parsing error exploited above yet get
the CSS box model right and understand the CSS2 parent-child selector. ALWAYS include
a "be nice to Opera 5" rule every time you use the Tantek Celik hack (above). */
body>#Header {height:14px;}

#Content {
        margin:0px 50px 50px 200px;
        padding:10px;
        }

#Menu {
        position:absolute;
        top:100px;
        left:20px;
        width:172px;
        padding:10px;
        background-color:rgb(100,100,100);
        border:1px dashed #999;
        line-height:17px;
/* Again, the ugly brilliant hack. */
        voice-family: "\"}\"";
        voice-family:inherit;
        width:150px;
        }
/* Again, "be nice to Opera 5". */
body>#Menu {width:150px;}

</style>

environment.php

<html>
 <head>
  <title>Environment</title>
<link href="environment.css" rel="stylesheet" type="text/css">
 </head>
 <body>

<?php
parse_str($_SERVER['QUERY_STRING'],$trailer);
$location=$trailer['location'];
if ($location == "") {
    $location="Crawlspace";
}
?>

<div id="Header">
Environment
</div>


<div id="Menu">
   <a href="environment.php?location=Attic"><img src="graph.php?location=Attic&output=display" width=100% ></a>
   <DIV ALIGN=CENTER>Attic</DIV>
   <a href="environment.php?location=Crawlspace"><img src="graph.php?location=Crawlspace&output=display" width=100% ></a>
   <DIV ALIGN=CENTER>Crawlspace</DIV>

</div>

<div id="Content">
<?php
   echo "<DIV ALIGN=CENTER><h1>$location</h1></DIV>";
   if ($location == "Attic") {
   echo '<img src="graph.php?location=Attic&output=display" width=100% height=800px>';
   }
   else if ($location == "Crawlspace") {
   echo '<img src="graph.php?location=Crawlspace&output=display" width=100% height=800px>';
   }
?>
</div>

 <?php
?>

 </body>
</html>

graph.php

<?php
include("phpgraphlib.php");

$link = mysql_connect('localhost', 'db_user_name', 'db_user_password')
  or die('Could not connect: ' . mysql_error());

mysql_select_db('Temp_Humi_Gas') or die('Could not select database');

mysql_query("DELETE FROM logtable WHERE timestamp < DATE_SUB(NOW(), INTERVAL 3 DAY);");

parse_str($_SERVER['QUERY_STRING'],$trailer);
$location=$trailer['location'];
$imageordisplay=$trailer['output'];

$xsize=1280;
$ysize=1024;


if ($imageordisplay == "display") {
    $graph=new PHPGraphLib($xsize,$ysize);
}
else if ($imageordisplay == "image") {
    $fname=date("Y-m-d") . "-" . $location . ".png";
    touch($fname);
    $graph=new PHPGraphLib($xsize,$ysize, $fname);
}
else {
    $imageordisplay="display";
    $graph=new PHPGraphLib($xsize,$ysize);
}

//invoke graph.php with the following parameters:
//?location=Attic&output=display"
//?location=Crawlspace
//?output=display
//?output=image
// e.g. graph.php?location=Crawlspace&output=image" 

$dataArray=array();


// mysql query is executed
$logdata = mysql_query("(Select * from logtable ORDER by timestamp DESC LIMIT 144) ORDER by timestamp");

// empty array initialized
$temp1 = Array();
$temp2 = Array();
$humi1 = Array();
$humi2 = Array();

// while there are results of the executed mysql query
while ( $row = mysql_fetch_assoc($logdata) )
{
   // create a new element in $logdata array and put data there
   $temp1[ $row['timestamp']] = $row['temp1'];
   $humi1[] = $row['humi1'];
   $temp2[ $row['timestamp']] = $row['temp2'];
   $humi2[] = $row['humi2'];
}


//configure graph
$graph->setBars(false);
$graph->setLine(true);
//$graph->setGoalLine(50);
$graph->setLineColor("red","blue");
if ($location=="Attic") {
    $graph->addData($temp1, $humi1);
    $graph->setTitle("Attic Temperature / Humidity");
}
else {
    $graph->addData($temp2, $humi2);
    $graph->setTitle("Crawlspace Temperature / Humidity");
}
$graph->setGridColor("149,154,87");
//$graph->setDataPoints(true);
//$graph->setDataPointSize(2);
//$graph->setDataPointColor("red");
$graph->setGradient("lime", "green");
$graph->setBarOutlineColor("black");
$graph->setBackgroundColor("183,183,183");
$graph->setLegend(true);
$graph->setLegendTextColor("red");
$graph->setXAxisTextColor("black");
$graph->setupYAxis(3, "red");
$graph->setXValuesInterval(5);
$graph->setLegendOutlineColor("gray");
$graph->setLegendTitle('Temperature', 'Humidity');
$graph->createGraph();
?>

Remember, at the top of graph.php change db_user_name and db_user_password to the user and password for your MySQL server.

Every time graph.php runs, it prunes the database, leaving the last three days. If you don't want this to happen, modify:
mysql_query("DELETE FROM logtable WHERE timestamp < DATE_SUB(NOW(), INTERVAL 3 DAY);");

Calling environment.php with different parameters will give you different results. For instance:
http://192.168.1.100/automation/environment.php?location=Attic will display a graph of the attic temperature and humidity for the past 24 hours.
http://192.168.1.100/automation/environment.php?location=Crawlspace will display the Crawlspace graph.
(of course change the IP address to the address of your web server)

Calling environment.php with an additional parameter &output=image, your browser will display nothing, but a PNG image file will be created in the same directory. The name of the file will be in the format e.g. 2014-06-19-Attic.PNG. This feature is used by a script called fetchgraphs.sh which I schedule every night at midnight.

fetchgraphs.sh

wget 'http://localhost/automation/graph.php?location=Attic&output=image'
rm 'graph.php?location=Attic&output=image'
wget 'http://localhost/automation/graph.php?location=Crawlspace&output=image'
rm 'graph.php?location=Crawlspace&output=image'
exit 0

Here is an example of the web page generated by environment.php:



Feel free to modify any of these files to suit your needs.

Hopefully I have provided enough detail to get you started. If you have any questions, contact me via the the Contact link, http://radio.wt4y.com/contact.

Update: I now use the Arduino to fetch temperature and humidity information and send to data to a MQTT broker, a much simpler solution.