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;