Weather Station with logging

Project status: HALTED

About this project

Project Weather Station is intended as a data collection and analysis project for temperature and relative humidity. All nodes can connect through WiFi to a central web server to store data. Through a web page this data can then be viewed in graphs, or downloaded in text/csv for analysis.

Hardware

For the node part:
– ESP-01 (ESP8266 module with 2x GPIO )
– DHT22 (Temperature and relative humidity sensor)
– 3V3 power supply (battery + regulator)

Known issues

After x amount of time, the WiFi stops working (R1). Even when the radio is put to sleep after a successful data point upload, and woken up some time later, eventually the system fails (R2). Not sure if this is a board issue or a software issue. Further investigation needed.

Source files

Even so, the source files in their current state (unfinished but working) are all available below:

Arduino code for weather station nodes

Code is written for the ESP-01 (also known as the ESP8266 or ESP8266-01)
Code below is revision 2
Required libraries are specified in the comments

/*
  - Setup
    - Start serial monitor
    - Connect to WiFi
  - Loop
    - Read temperature and humidity every x ms (default 2s)
    - Write temperature and humidity every y ms (default 30s)

  In this version, there is WAKE FROM DEEP SLEEP functionality
  Note: On ESP-01, some soldering from chip pin to RST pin needs to be done.

  Libraries to include:
  - http://arduino.esp8266.com/stable/package_esp8266com_index.json [Additional Board Manager URLs]
  - AutoConnect by Hieromon Ikasamo [Library Manager]
  - PageBuilder by Hieromon Ikasamo [Library Manager]
  - SimpleDHT (https://github.com/winlinvip/SimpleDHT) [Library Manager ; ZIP library]
*/

// Simple DHT library
#include <SimpleDHT.h>

// ESP8266 WiFi libraries
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

/*
  DHT11
    20-80% humidity readings with 5% accuracy
    0-50°C temperature readings ±2°C accuracy
    No more than 1 Hz sampling rate (once every second)

  DHT22
    0-100% humidity readings with 2-5% accuracy
    -40 to 80°C temperature readings ±0.5°C accuracy
    No more than 0.5 Hz sampling rate (once every 2 seconds)
*/

/*
  ESP-01
    Use GPIO2 (pin 3)
    Set GPIO0 (pin 5) LOW (to GND) when booting to enter program mode

  ESP-12E (NodeMCU)
    DO NOT USE GPIO2 (D4) for the DHT22 !!!!!
*/

// Settings for DHT22
int pinDHT22 = 2; 				// GPIO2 on ESP-01
SimpleDHT22 dht22(pinDHT22);

// Settings for WiFi
String WiFiSSID = "<WiFi SSID>";
String WiFiPWD = "<WiFi password>";
String WiFiPage = "<url>/add_point.php";
int SensorNumber = 2;   // Unique number per node

// Intervals
/*
  Every min.  #/hour  #/day
  5           12      288   --> high precision
  15          4       96    --> good value
  30          2       48
  60          1       24    --> daily data
*/
int intervalPoll = 2000;    // Poll sensor every 2000 ms (2s)
int intervalWrite = 15 * 60 * 1000;  // Write sensor data to database every 15 minutes

// Global vars (no need to change)
unsigned long lastWrite;
unsigned long lastPoll;
float temperature = 0;
float humidity = 0;
int err = 0;
bool bFirstWrite = true;
bool bWriteSuccess = false;

/********************************************************************************
    SETUP
 ********************************************************************************/
void setup()
{
  // Start serial monitor
  Serial.begin(115200);
  while (!Serial) {
    ; // Wait for serial port
  }
  Serial.println("--- Serial monitor started ---");

  // Connect WiFi
  WiFiConnect();
  delay(500);
}

/********************************************************************************
    LOOP
 ********************************************************************************/
void loop()
{
  // lastPoll and lastWrite vs millis()
  // unsigned long = uint32_t = 32 bit UINT --> 0..4,294,967,295 ms = 49.7 days

  // Start polling because data needs to be written
  if (!bWriteSuccess)
  {
    // Refresh sensor data
    if (lastPoll + intervalPoll < millis() || lastPoll > millis())
    {
      err = readSensor(); // Read sensor and wait for error code return (0 = no error)
      if (err == 0)
      {
        Serial.print((String)temperature);
        Serial.print("°C | ");
        Serial.print((String)humidity);
        Serial.println("%RH");
      }
      else
      {
        Serial.print("Read DHT22 failed, err=");
        Serial.println((String)err);
      }
      lastPoll = millis();
    }
  }

  // Write to database every ___s
  if (lastWrite + intervalWrite < millis() || lastWrite > millis() || bFirstWrite)
  {
    bWriteSuccess = false;  // Keep trying untill data is written

    // Is WiFi up ?
    if (WiFi.status() != WL_CONNECTED)
    {
      Serial.println("Waking up WiFi");
      WiFiConnect();
      delay(500);
    }
    // Only write if last read sensor returned no error and WiFi is connected
    else if (err == 0 && WiFi.status() == WL_CONNECTED)
    {
      Serial.print((String)temperature);
      Serial.print("°C | ");
      Serial.print((String)humidity);
      Serial.println("%RH ==> WRITING");
      String data;
      data = "temp=";
      data += (String)temperature;
      data += "&hum=";
      data += (String)humidity;
      data += "&sensor=";
      data += (String)SensorNumber;  // sensor number

      // Send data to web server with method POST
      HTTPClient http;
      http.begin(WiFiPage);         // Call page
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");  // Send header for web server
      http.POST(data);                                                      // Send POST request to web server
      http.writeToStream(&Serial);                                          // Prints result on Serial
      http.end();

      bFirstWrite = false;
      bWriteSuccess = true; // Data written ... set flag and turn off WiFi
      WiFi.disconnect();	// Consumption down with > 50%
      delay(500);
      lastWrite = millis();
    }
    else
    {
      // If error, try again in 2s
      lastWrite = millis() - (intervalWrite - 2000); // Try again after 2s
    }
  }

}


/********************************************************************************
    functions
 ********************************************************************************/

// read sensor
int readSensor()
{
  err = dht22.read2(&temperature, &humidity, NULL);
}


/*
  WiFi.status()
  0:    WL_IDLE_STATUS      (When not connected to network bt powered on)
  1:    WL_NO_SSID_AVAIL
  2:    WL_SCAN_COMPLETED
  3:    WL_CONNECTED
  4:    WL_CONNECT_FAILED
  5:    WL_CONNECTION_LOST
  6:    WL_DISCONNECTED
  255:  WL_NO_SHIELD
*/


// Turn WiFi on and connect to network
void WiFiConnect()
{
  int retries = 0;
  ESP.eraseConfig();        // Erases any WPS config
  WiFi.disconnect(true);    // Erase SSID and PWD
  delay(500);
  WiFi.setAutoConnect(false);   // Do not auto connect at power up
  WiFi.mode(WIFI_STA);          // Set at STAtion (Connect to AP)
  /*
    WIFI_PHY_MODE_11B : 802.11b, more range, low Transfer rate, more current draw
    WIFI_PHY_MODE_11G : 802.11g, medium range, medium transfer rate, medium current draw
    WIFI_PHY_MODE_11N : 802.11n, least range, fast transfer rate, least current draw (STATION ONLY)
  */
  WiFi.setPhyMode(WIFI_PHY_MODE_11G); // Difference between B/G/N not measurable ???
  delay(500);
  /*
    // Static network configuration --> Comment for DHCP
    IPAddress ip(192, 168, 4, 4);
    IPAddress gateway(192, 168, 4, 1);
    IPAddress subnet(255, 255, 255, 0);
    WiFi.config(ip, gateway, subnet);
  */
  WiFi.begin(WiFiSSID, WiFiPWD);        // Connect to AP
  WiFi.printDiag(Serial);               // Wi-Fi diagnostic information to Serial
  // Serial.println(WiFi.getPhyMode()); // Check is PhyMode is what we expect
  Serial.print("Connecting .");

  // Waiting for connection ...
  while (WiFi.status() != WL_CONNECTED)
  {
    // Only try 10 times
    if (retries > 10)
    {
      Serial.println(" - can't connect, going to deep sleep");
      WiFi.disconnect();
      /*
        ESP.deepSleep(microseconds, mode) will put the chip into deep sleep.
        mode is one of WAKE_RF_DEFAULT, WAKE_RFCAL, WAKE_NO_RFCAL, WAKE_RF_DISABLED.
        ESP01: Solder GPIO16 to RESET, in order to be able to wake up
      */
      ESP.deepSleep(1000 * 1000 * 60, WAKE_RFCAL);   // Hybernate
      delay(500);
      break;
    }
    delay(1000);        // Wait 1000ms between each try
    Serial.print(".");
    retries++;
  }

  // If WiFi connected, display IP
  if (WiFi.status() == WL_CONNECTED)
  {
    Serial.println();
    Serial.print("Connected, IP address: ");
    Serial.println(WiFi.localIP());    // Print IP address
  }
}

php pages for data collection / analysis

Web pages are tested with
– apache 2.2 (and should work in apache 2.4 as well)
– php 5.6 (and should work in php 7.2 as well)
– MariaDB 10 (MySQL database)

Required libraries:
MySQL class: provided in the list below
JpGraph (v4.2.7): can be downloaded here

<?php
/*****************************************************************************
 *
 *	Class: MySQL
 *	Description: Class to handle mysql functionality using mysqli (php 5.6 and later)
 *
 *    Version     Updated         By
 *    1.0         21/03/2019      Arashi Projects
 *    1.1         22/07/2019      Arashi Projects	- Bugfix: Warning: mysqli_errno() expects exactly 1 parameter, 0 given
 *
 *****************************************************************************/


class MySQL
{
	private $host;			// MySQL server
	private $login;			// MySQL login
	private $passwd;		// MySQL password
	private $database;		// MySQL database

	private $link;			// Connection
	private $recordset;		// recordset
	private $affected = 0;	// affected rows after (onsert, update, delete, ...)


	/*****************************************************************************
	 *   Constructor
	 *   Optional arguments are 'host', 'login', 'password', 'database'
	 *****************************************************************************/
	public function __construct($host = "localhost", $login = "", $pwd = "", $db = "")
	{
		$this->host = $host;
		$this->login = $login;
		$this->passwd = $pwd;
		$this->database = $db;
	}

	/*****************************************************************************
	 *   Destructor
	 *****************************************************************************/
	public function __destruct()
	{
		// Disconnect is there's a link
		if ( isset($this->link) )
		{ $this->disconnect(); }
	}

	/*****************************************************************************
	 *  Connect
	 *  Connects to 'host' with 'login' and 'passwd' and selects 'database'
	 *****************************************************************************/
	public function connect()
	{
		// State flag, if a step fails, skip all following steps
		$bResult = true;

		// Check any previous connection
		if ($bResult AND $this->link )
		{
			$bResult = false;
			throw new Exception("Connection is already open.", -1);
		}

		// Check if hostname is set
		if ($bResult AND $this->host == "" )
		{
			$bResult = false;
			throw new Exception("Host is not specified.", -1);
		}

		// Check if login is set
		if ($bResult AND $this->login == "" )
		{
			$bResult = false;
			throw new Exception("Login is not specified.", -1);
		}

		// Check if password is set (can be empty)
		if ($bResult AND !isset($this->passwd) )
		{
			$bResult = false;
			throw new Exception("Password is not specified.", -1);
		}

		// Check if database is set
		if ($bResult AND $this->database == "" )
		{
			$bResult = false;
			throw new Exception("Database is not specified.", -1);
		}

		// Open connection to server
		if ($bResult)
		{
			$link = mysqli_connect($this->host, $this->login, $this->passwd, $this->database);

			if ( ! $link )
			{
				$bResult = false;
				throw new Exception(mysqli_connect_error(), mysqli_connect_errno());
			}
			else
			{
				// Pass on the link here - connection is made
				$this->link = $link;
			}
		}

		// Return the result
		return $bResult;
	}




	/*****************************************************************************
	 *  Disonnect
	 *  Disconnects from the server
	 *****************************************************************************/
	public function disconnect()
	{
		// State flag, if a step fails, skip all following steps
		$bResult = true;

		// Check any connection
		if ($bResult AND ! $this->link )
		{
			$bResult = false;
			throw new Exception("No connection found.", -1);
		}

		// close the connection
		if ( $bResult AND ! mysqli_close($this->link) )
		{
			$bResult = false;
			throw new Exception(mysqli_connect_error(), mysqli_connect_errno());
		}
		// clean up link anyway - in case of fail, link will be cleaned
		unset($this->link);

		// Return result
		return $bResult;
	}




	/*****************************************************************************
	 *  Query
	 *  Executes the query
	 *****************************************************************************/
	public function query($query)
	{
		// Set affected rows to 0 first
		$this->affected = 0;
		
		// State flag, if a step fails, skip all following steps
		$bResult = true;

		// Free up result and recordset
		if ($bResult and isset($this->recordset))
		{
			unset($this->recordset);	// Clear the recordset
		}

		// Check for connections
		if ($bResult AND !isset($this->link) )
		{
			$bResult = false;
			throw new Exception("No connection found", -1);
		}

		// Execute the querry
		if ($bResult)
		{
			$queryresult = mysqli_query($this->link, $query) ;

			if ( $queryresult === false )		// Mostly the error will be fatal (wrong fieldname etc ..)
			{
				//For SQL statements, INSERT, UPDATE, DELETE, DROP, etc,
				// mysqli_query() returns TRUE on success or FALSE on error.
				$bResult = false;
				throw new Exception(mysqli_error($this->link), mysqli_errno($this->link));
			}
			elseif ( $queryresult === true )
			{
				//For SQL statements, INSERT, UPDATE, DELETE, DROP, etc,
				// mysqli_query() returns TRUE on success or FALSE on error.
				$bResult = true;

				// Don't free the queryresult on true (causing warning)
				
				// ADDED 25/11/2009 - remember affected rows
				$this->affected = mysqli_affected_rows($this->link);
			}
			else	// Valid query but no records found will also end up here (SELECT)
			{
				// For SELECT, SHOW, DESCRIBE, EXPLAIN and other statements returning resultset,
				// mysqli_query() returns a resource on success, or FALSE on error.
				$bResult = true;
				
				// Recordset returned
				$arrRS = array();
	
				// Create a recordset
				while ($row = mysqli_fetch_assoc($queryresult))
				{ $arrRS[] = $row; }

				// store the recordset
				$this->recordset = $arrRS;
				
				// Free the queryresult
				mysqli_free_result($queryresult);
			}
		}
		
		// return the result
		return $bResult;
	}


	/*****************************************************************************
	 *  READ OUT
	 *****************************************************************************/
	// Get the number of affected rows
	public function affected_rows()
	{ return $this->affected; }
	

	/*****************************************************************************
	 *  ACTIONS for the recordset
	 *****************************************************************************/

	// Move to the first element in the recordset
	public function rsReset()
	{ reset($this->recordset); }

	// Move to the last element in the recordset
	public function rsEnd()
	{ end($this->recordset); }
	
	// Move to the next element in the recordset
	public function rsNext()
	{ next($this->recordset); }
	
	// Move to the previous element in the recordset
	public function rsPrev()
	{ prev($this->recordset); }
	
	// return the current element in the recordset
	public function rsGet($field)
	{
		$current = current($this->recordset);
		return $current[$field];
	}
	
	// Check end
	public function rsEOF()
	{
		$current = current($this->recordset);
		if ($current === false) {return true;} else {return false;}
	}
	
	// return the number of rows
	public function rsNumRows()
	{ return count($this->recordset); }
	
	// return the number of fields
	public function rsNumFields()
	{ return count(current($this->recordset)); }
	
	// return the entire current row
	public function rsGetRow()
	{ return current($this->recordset); }
	
	// return all fieldnames for this row
	public function rsGetFieldnames()
	{ return array_keys(current($this->recordset)); }
	

	// return entire recordset
	public function rsGetRecordSet()
	{ return $this->recordset; }
	
	
	
	/*****************************************************************************
	 *  Properties
	 *****************************************************************************/

	// change host
	public function setHost($host)
	{
		if ( empty($host) )
		{ throw new Exception('Host cannot be empty', -1); }
		else
		{ $this->host = $host; }
	}
	
	// change login
	public function setLogin($login)
	{
		if ( empty($login) )
		{ throw new Exception('Login cannot be empty', -1); }
		else
		{ $this->login = $login; }
	}

	// change password
	public function setPassword($passwd)
	{
		// Password can be empty
		$this->passwd = $passwd;
	}

	// change database
	public function setDatabase($db)
	{
		if ( empty($db) )
		{ throw new Exception('Database cannot be empty', -1); }
		else
		{ $this->database = $db; }
	}

	
}  // End CLASS MySQLi

?>
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
</head>
<body>

<?php


if (isset($_GET) and !empty($_GET))
{
	// GET detected --> read values from database
	buildList();
	readfromDatabase();
}
else
{
	// No POST/GET --> list
	buildList();
}

?>

</body>
</html>


<?php


function readfromDatabase()
{
	try {
		require_once('class_mysqli.php');
		$con = new MySQL("<host>", "<login>", "<password>" , "<database>");
		$con->connect();
/*
		$query = "SELECT * FROM";
		$query .= " (SELECT * FROM tblWeather WHERE Sensor=1 ORDER BY Timestamp DESC LIMIT 30)";
		$query .= " SUB ORDER BY Timestamp ASC";
		$query .= ";";
*/
		$query = "SELECT * FROM tblWeather WHERE";
		$query .= " Sensor='".$_GET['sensor']."'";
		$query .= " AND";
		$query .= " Timestamp >= '".$_GET['startdate']."'";
		$query .= " AND";
		$query .= " Timestamp <= '".$_GET['enddate']."'";
		$query .= " ORDER BY Timestamp ASC";
		$query .= " LIMIT ".$_GET['lines'];
		$query .= ";";

		// Get average temp. and hum. per hour per day
		$queryAVG = "SELECT";
		$queryAVG .= "   date_format(Timestamp, '%Y/%m/%d' ) AS myDay,";
		$queryAVG .= "   date_format(Timestamp, '%H:00' ) AS myHour,";
		$queryAVG .= "   ROUND(AVG(Temperature),2) as avgTemp,";
		$queryAVG .= "   ROUND(AVG(Humidity),2) as avgHum,";
		$queryAVG .= "   MIN(Temperature) as minTemp,";
		$queryAVG .= "   MAX(Temperature) as maxTemp";
		$queryAVG .= " FROM";
		$queryAVG .= "   tblWeather";
		$queryAVG .= " WHERE";
		$queryAVG .= "   Sensor='".$_GET['sensor']."'";
		$queryAVG .= " AND";
		$queryAVG .= "   Timestamp >= '".$_GET['startdate']."'";
		$queryAVG .= " AND";
		$queryAVG .= "   Timestamp <= '".$_GET['enddate']."'";
		$queryAVG .= " GROUP BY";
		$queryAVG .= "   date_format(Timestamp, '%Y%m%d' ),";
		$queryAVG .= "   date_format(Timestamp, '%H' )";
		$queryAVG .= " ORDER BY";
		$queryAVG .= "   Timestamp ASC";
//		$queryAVG .= " LIMIT";
//		$queryAVG .= "   ".$_GET['lines'];
		$queryAVG .= ";";

//		echo $queryAVG;

		$con->query($query);		
		// echo $con->rsNumRows()." records found<br />\n";
		$con->rsReset();
		$i = 0;
		echo "<p>\n";
		echo "<textarea cols=\"60\" rows=\"3\">\n";
		foreach ($con->rsGetRecordSet() as $key=>$value) {
			echo $i++ . " | ";
			echo $value['Timestamp'] . " | ";
			echo $value['Sensor'] . " | ";
			echo $value['Temperature'] . " °C | ";
			echo $value['Humidity'] . " %RH";
			echo "\n";
		}
		echo "</textarea><br />\n";
		echo "</p>\n";
		$con->disconnect();
	}
	catch (Exception $e) {
		echo "<p>Error ".$e->getCode().": ".$e->getMessage()."</p>\n";
	}

	$getdata = "?sensor=" . $_GET['sensor'];
	$getdata .= "&startdate=" . $_GET['startdate'];
	$getdata .= "&enddate=" . $_GET['enddate'];
	$getdata .= "&lines=" . $_GET['lines']; 

	echo "<img src=\"graph.php".$getdata."&graph=temp\" /> \n";
	echo "<img src=\"graph.php".$getdata."&graph=hum\" /> \n";

	echo "<p><img src=\"graph2.php".$getdata."\" /></p>\n";

}


function buildList()
{
	echo "<form method=\"GET\" action=\"".$_SERVER['PHP_SELF']."\">\n";

	// If startdate is not specified, set to today's date at 0:00
	if (isset($_GET['startdate']))
	{ $startdate = $_GET['startdate']; }
	else
	{ $startdate = date("Y-m-d 00:00:00"); }

	// If enddate is not specified, set to today's date at 23:59
	if (isset($_GET['enddate']))
	{ $enddate = $_GET['enddate']; }
	else
	{ $enddate = date("Y-m-d 23:59:59"); }

	echo "<label>Sensor</label><br />\n";
	echo "<select name=\"sensor\" size=\"4\">\n";
	echo "<option value=\"1\" ".($_GET['sensor']==1 || $_GET['sensor']==""?"selected":"").">Sensor 1 (Gameroom)<br />\n";
	echo "<option value=\"2\" ".($_GET['sensor']==2?"selected":"").">Sensor 2 (Outside)<br />\n";
	echo "<option value=\"3\" ".($_GET['sensor']==3?"selected":"").">Sensor 3 (reserved)<br />\n"; 
	echo "</select><br />\n";

/*
	// If none selected, select 1 by default
	if (!is_array($_GET['sensors']))
	{
		$_GET['sensors'] = array();
		$_GET['sensors'][] = 1;
	}
	echo "<select name=\"sensors[]\" size=\"4\" multiple>\n";
	echo "<option value=\"1\" ".(in_array("1", $_GET['sensors'])?"selected":"").">Sensor 1 (Gameroom)<br />\n";
	echo "<option value=\"2\" ".(in_array("2", $_GET['sensors'])?"selected":"").">Sensor 2 (Outside)<br />\n";
	echo "<option value=\"3\" ".(in_array("3", $_GET['sensors'])?"selected":"").">Sensor 3 (reserved)<br />\n"; 
	echo "</select><br />\n";
	echo "<i>Hold CTRL to select multiple<br />\n";
*/
	echo "<br />\n";
	echo "<label>Startdate - Enddate</label><br />\n";
	echo "<input type=\"input\" name=\"startdate\" value=\"".$startdate."\"> - \n";
	echo "<input type=\"input\" name=\"enddate\" value=\"".$enddate."\"><br />\n";
	echo "<br />\n";
	echo "<label>Lines</label>\n";
	echo "<select name=\"lines\">\n";
	echo "	<option value=\"20\" ".($_GET['lines']==20?"selected":"").">20</option>\n";
	echo "	<option value=\"50\" ".($_GET['lines']==50 || $_GET['lines']==""?"selected":"").">50</option>\n";
	echo "	<option value=\"100\" ".($_GET['lines']==100?"selected":"").">100</option>\n";
	echo "	<option value=\"250\" ".($_GET['lines']==250?"selected":"").">250</option>\n";
	echo "	<option value=\"3000\" ".($_GET['lines']==3000?"selected":"").">3000 (use with caution)</option>\n";
	echo "</select><br />\n";
	echo "<br />\n";
	echo "<input type=\"submit\" value=\"Show\"><br />\n";

	echo "</form>\n";
}




/*

SELECT 
  date_format( `Timestamp`, '%Y%m%d' ) AS myDay,
  date_format( `Timestamp`, '%H:00' ) AS myHour,
  AVG(`Temperature`)
FROM `tblWeather`
GROUP BY
  date_format( `Timestamp`, '%Y%m%d' ),
  date_format( `Timestamp`, '%H' )

*/

?>


<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
</head>
<body>

<?php



/*
  DHT11
    20-80% humidity readings with 5% accuracy
    0-50°C temperature readings ±2°C accuracy
    No more than 1 Hz sampling rate (once every second)

  DHT22
    0-100% humidity readings with 2-5% accuracy
    -40 to 80°C temperature readings ±0.5°C accuracy
    No more than 0.5 Hz sampling rate (once every 2 seconds)
*/

if (isset($_POST) and !empty($_POST))
{
	// POST detected --> write values to database if all data is valid
	$allValid = true;
	echo "<p>\n";
	// Is Sensor Number valid ?
	if ($_POST['sensor'] >= 0 and $_POST['sensor'] < 30)
	{ echo "Found sensor number: " . $_POST['sensor'] . "<br />\n"; }
	else
	{
		echo "Sensor number: INVALID<br />\n";
		$allValid = false;
	}
	// Is temperature valid ?
	// if ($_POST['temp'] >= 0 and $_POST['temp'] <= 50)	// DHT11
	if ($_POST['temp'] >= -40 and $_POST['temp'] <= 80)		// DHT22
	{ echo "Temperature: " . $_POST['temp'] . " °C<br />\n"; }
	else
	{
		echo "Temperature: INVALID<br />\n";
		$allValid = false;
	}
	// Is humidity valid ?
	// if ($_POST['hum'] >= 20 and $_POST['hum'] <= 80)		// DHT11
	if ($_POST['hum'] >= 0 and $_POST['hum'] <= 100)		// DHT22
	{ echo "Relative humidity: " . $_POST['hum'] . " %<br />\n"; }
	else
	{
		echo "Relative humidity: INVALID<br />\n";
		$allValid = false;
	}
	// If all data is valid --> write to database
	if ($allValid)
	{ 
		echo "All data valid; writing to database ...<br />\n";
		writeToDatabase($_POST['sensor'], $_POST['temp'], $_POST['hum']);
	}
	else
	{ echo "Cannot write invalid data to database.<br />\n"; }
	echo "</p>\n";

}
else
{
	// No POST --> Just send message
	echo "<p>\n";
	echo "No data found<br />\n";
	echo "</p>\n";
}

?>

</body>
</html>


<?php

function writeToDatabase($id, $temp, $hum)
{
	try {
		require_once('class_mysqli.php');
		$con = new MySQL("<host>", "<login>", "<password>" , "<database>");
		$con->connect();
		$query = "INSERT INTO tblWeather VALUES(";
		$query .= "default". ",";
		$query .= "NOW()". ",";
		$query .= $id. ",";
		$query .= $temp. ",";
		$query .= $hum;
		$query .= ");";
		// echo "<p>" . $query . "<p/>\n";
		$con->query($query);
		$con->disconnect();
	}
	catch (Exception $e) {
		echo "<p>Error ".$e->getCode().": ".$e->getMessage()."</p>\n";
	}
}



?>
<?php
	require_once('class_mysqli.php');
	try {
		$con = new MySQL("<host>", "<login>", "<password>" , "<database>");
		$con->connect();

		$query = "SELECT * FROM tblWeather WHERE";
		$query .= " sensor='".$_GET['sensor']."'";
		$query .= " AND";
		$query .= " Timestamp >= '".$_GET['startdate']."'";
		$query .= " AND";
		$query .= " Timestamp <= '".$_GET['enddate']."'";
		$query .= " ORDER BY Timestamp ASC";
		$query .= " LIMIT ".$_GET['lines'];
		$query .= ";";

		$con->query($query);
		
		$con->rsReset();
		foreach ($con->rsGetRecordSet() as $key=>$value) {
			// $arrTime[] = $value['Timestamp'];
			// $arrTime[] = date('Y-m-d H:i:s', strtotime($value['Timestamp']));
			$arrTime[] = date('d/m - H:i', strtotime($value['Timestamp']));
			$arrTemp[] = $value['Temperature'];
			$arrHum[] = $value['Humidity'];
		}
		$con->disconnect();
	}
	catch (Exception $e) {
		echo "<p>Error ".$e->getCode().": ".$e->getMessage()."</p>\n";
	}

	require_once("jpgraph/src/jpgraph.php");
	require_once("jpgraph/src/jpgraph_line.php");
	
	$graph = new Graph(600, 300); // width, height
	$graph->SetScale('intint'); //set the scale of the graph

	$graph->SetMargin(30,15,30,100);	// Left, Right, Top, Bottom
	$graph->SetFrame(true,'black',1);

	// Setup month as labels on the X-axis
	$graph->xaxis->SetTickLabels($arrTime);
	$graph->xaxis->SetLabelAngle(60);
	//$graph->SetShadow(); 

	if ($_GET['graph'] == 'temp')
	{
		$graph->title->Set('Temperature (°C)');		// Setup a title for the graph
		// $graph->xaxis->title->Set('Entry');		// Setup titles and X-axis labels
		// $graph->yaxis->title->Set('°C');		// Setup Y-axis title
		$plot = new LinePlot($arrTemp);
	}
	elseif ($_GET['graph'] == 'hum')
	{
		$graph->title->Set('Humidity (%RH)');			// Setup a title for the graph
		// $graph->xaxis->title->Set('Entry');		// Setup titles and X-axis labels
		// $graph->yaxis->title->Set('%RH');		// Setup Y-axis title
		$plot = new LinePlot($arrHum);
	}
	$graph->Add($plot);
	$graph->Stroke();

	/*
		// Display both in 1 graph
		$plot1 = new LinePlot($dat1);
		$plot2 = new LinePlot($dat2);
		$graph->Add($plot1);
		$graph->Add($plot2);
		$graph->Stroke();
	*/

?>
<?php
	require_once('class_mysqli.php');
	try {
		$con = new MySQL("<host>", "<login>", "<password>" , "<database>");
		$con->connect();

		// Get average temp. and hum. per hour per day
		$query = "SELECT";
		$query .= "   date_format(Timestamp, '%m/%d' ) AS myDay,";
		$query .= "   date_format(Timestamp, '%H:00' ) AS myHour,";
		$query .= "   ROUND(AVG(Temperature),2) as avgTemp,";
		$query .= "   ROUND(AVG(Humidity),2) as avgHum,";
		$query .= "   MIN(Temperature) as minTemp,";
		$query .= "   MAX(Temperature) as maxTemp";
		$query .= " FROM";
		$query .= "   tblWeather";
		$query .= " WHERE";
		$query .= "   Sensor='".$_GET['sensor']."'";
		$query .= " AND";
		$query .= "   Timestamp >= '".$_GET['startdate']."'";
		$query .= " AND";
		$query .= "   Timestamp <= '".$_GET['enddate']."'";
		$query .= " GROUP BY";
		$query .= "   date_format(Timestamp, '%Y%m%d' ),";
		$query .= "   date_format(Timestamp, '%H' )";
		$query .= " ORDER BY";
		$query .= "   Timestamp ASC";
//		$query .= " LIMIT";
//		$query .= "   ".$_GET['lines'];
		$query .= ";";

		$con->query($query);
		
		$con->rsReset();
		foreach ($con->rsGetRecordSet() as $key=>$value) {
			// $arrTime[] = $value['Timestamp'];
			// $arrTime[] = date('Y-m-d H:i:s', strtotime($value['Timestamp']));
			$arrTime[] = $value['myDay'] . " " . $value['myHour'];
			$avgTemp[] = $value['avgTemp'];
			$minTemp[] = $value['minTemp'];
			$maxTemp[] = $value['maxTemp'];
		}
		$con->disconnect();
	}
	catch (Exception $e) {
		echo "<p>Error ".$e->getCode().": ".$e->getMessage()."</p>\n";
	}

	require_once("jpgraph/src/jpgraph.php");
	require_once("jpgraph/src/jpgraph_line.php");
	
	$graph = new Graph(1200, 500); // width, height
	$graph->SetScale('intint'); //set the scale of the graph

	$graph->SetMargin(30,15,30,100);	// Left, Right, Top, Bottom
	$graph->SetFrame(true,'black',1);

	// Setup month as labels on the X-axis
	$graph->xaxis->SetTickLabels($arrTime);
	$graph->xaxis->SetLabelAngle(60);
	//$graph->SetShadow(); 

	$graph->title->Set('Temperature (°C)');		// Setup a title for the graph
	$graph->legend->Hide(false);
	// $graph->xaxis->title->Set('Entry');		// Setup titles and X-axis labels
	// $graph->yaxis->title->Set('°C');		// Setup Y-axis title
	$plot1 = new LinePlot($avgTemp);
	$plot2 = new LinePlot($minTemp);
	$plot3 = new LinePlot($maxTemp);

	$graph ->legend->Pos( 0.001, 0.001, "left" ,"top");
	$plot1->SetLegend ("Average"); 
	$plot2->SetLegend ("Min."); 
	$plot3->SetLegend ("Max."); 

	$graph->Add($plot1);
	$graph->Add($plot2);
	$graph->Add($plot3);
	$graph->Stroke();

	/*
		// Display both in 1 graph
		$plot1 = new LinePlot($dat1);
		$plot2 = new LinePlot($dat2);
		$graph->Add($plot1);
		$graph->Add($plot2);
		$graph->Stroke();
	*/

?>

MySQL database

ISAM database used for data collection

--
-- Table structure for table `tblWeather`
--

CREATE TABLE `tblWeather` (
  `id_entry` int(11) NOT NULL,
  `Timestamp` datetime NOT NULL,
  `Sensor` tinyint(4) NOT NULL,
  `Temperature` float NOT NULL,
  `Humidity` float NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- Indexes for dumped tables
--

--
-- Indexes for table `tblWeather`
--
ALTER TABLE `tblWeather`
  ADD PRIMARY KEY (`id_entry`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `tblWeather`
--
ALTER TABLE `tblWeather`
  MODIFY `id_entry` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;