Advertisement
Ruddog

Temperature and Humidity Web Server by David Bird

Jan 15th, 2018
494
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 38.26 KB | None | 0 0
  1. /* ESP8266 plus WEMOS DHT11 Sensor with a Temperature and Humidity Web Server
  2.  Automous display of sensor results on a line-chart, gauge view and the ability to export the data via copy/paste for direct input to MS-Excel
  3.  The 'MIT License (MIT) Copyright (c) 2016 by David Bird'. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  4.  documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,
  5.  distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
  6.  following conditions:
  7.    The above copyright ('as annotated') notice and this permission notice shall be included in all copies or substantial portions of the Software and where the
  8.    software use is visible to an end-user.
  9.    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  10.    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  11.    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  12. See more at http://dsbird.org.uk
  13. */
  14.  
  15. #include <ESP8266WiFi.h>
  16.  
  17.  
  18. #include <ESP8266WebServer.h>
  19. #include <DNSServer.h>
  20. #include <WiFiManager.h>     // https://github.com/tzapu/WiFiManager
  21. #include <SPI.h>
  22. #include <SD.h>              // Needs to be version 1.0.9 to work with ESP8266
  23. #include <time.h>        
  24. #include <Adafruit_Sensor.h>
  25. #include <DHT.h>
  26. #include <DHT_U.h>
  27. #include "credentials.h"
  28. #define DHTPIN     D4  // Pin which is connected to the DHT sensor.
  29. // Uncomment the type of sensor in use:
  30. #define DHTTYPE           DHT11     // DHT 11
  31. //#define DHTTYPE           DHT22     // DHT 22 (AM2302)
  32. //#define DHTTYPE           DHT21     // DHT 21 (AM2301)
  33. DHT_Unified dht(DHTPIN, DHTTYPE,16); // Use 16 for the ESP8266
  34.  
  35. String version = "v1.0";      // Version of this program
  36. WiFiClient client;
  37. ESP8266WebServer server(80); // Start server on port 80 (default for a web-browser, change to your requirements, e.g. 8080 if your Router uses port 80
  38.                              // To access server from the outsid of a WiFi network e.g. ESP8266WebServer server(8266); and then add a rule on your Router that forwards a
  39.                              // connection request to http://your_network_ip_address:8266 to port 8266 and view your ESP server from anywhere.
  40.                              // Example http://g6ejd.uk.to:8266 will be directed to http://192.168.0.40:8266 or whatever IP address your router gives to this server
  41.  
  42. int       log_time_unit = 15;   // default is 1-minute between readings, 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr
  43. int       time_reference= 60;   // Time reference for calculating /log-time (nearly in secs) to convert to minutes
  44. int const table_size    = 288;  // 300 is about the maximum for the available memory, so use 12 samples/hour * 24 * 1-day = 288
  45. int       index_ptr, timer_cnt, log_interval, log_count, max_temp, min_temp;
  46. String    webpage,time_now,log_time,lastcall;
  47. bool      SD_present, batch_not_written, AScale, auto_smooth, AUpdate, log_delete_approved;
  48. float     dht_temp,dht_humi;
  49.  
  50. typedef struct {
  51.   int     lcnt;  // Sequential log count
  52.   String ltime;  // Time reading taken
  53.   sint16_t temp; // Temperature values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution
  54.   sint16_t humi; // Humidity values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution
  55. } record_type;
  56.  
  57. record_type sensor_data[table_size+1]; // Define the data array
  58.  
  59.  
  60. void setup() {
  61.  
  62.  
  63.  
  64.    
  65.   //WiFiManager intialisation. Once completed there is no need to repeat the process on the current board
  66.   WiFiManager wifiManager;
  67.   // New OOB ESP8266 has no Wi-Fi credentials so will connect and not need the next command to be uncommented and compiled in, a used one with incorrect credentials will
  68.   // so restart the ESP8266 and connect your PC to the wireless access point called 'ESP8266_AP' or whatever you call it below in ""
  69.    wifiManager.resetSettings(); // Command to be included if needed, then connect to http://192.168.4.1/ and follow instructions to make the WiFi connection
  70.   // Set a timeout until configuration is turned off, useful to retry or go to sleep in n-seconds
  71.   wifiManager.setTimeout(180);
  72.   //fetches ssid and password and tries to connect, if connections succeeds it starts an access point with the name called "ESP8266_AP" and waits in a blocking loop for configuration
  73.   if(!wifiManager.autoConnect("ESP8266_AP")) {
  74.     Serial.println(F("failed to connect and timeout occurred"));
  75.     delay(3000);
  76.     ESP.reset(); //reset and try again
  77.     delay(5000);
  78.   }
  79.  
  80.  
  81.   // At this stage the WiFi manager will have successfully connected to a network, or if not will try again in 180-seconds
  82.   //----------------------------------------------------------------------
  83.   Serial.println(F("WiFi connected.."));
  84.   configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov");
  85.   server.begin(); Serial.println(F("Webserver started...")); // Start the webserver
  86.   Serial.println("Use this URL to connect: http://"+WiFi.localIP().toString()+"/");// Print the IP address
  87.   //----------------------------------------------------------------------
  88.   Serial.print(F("Initializing SD card..."));
  89.   if (!SD.begin(D8)) { // see if the card is present and can be initialised. Wemos SD-Card CS uses D8
  90.     Serial.println(F("Card failed, or not present"));
  91.     SD_present = false;
  92.   } else SD_present = true;
  93.   if (SD_present) Serial.println(F("Card initialised.")); else Serial.println(F("Card not present, no SD Card data logging possible"));
  94.   dht.begin();
  95.   //----------------------------------------------------------------------  
  96.   server.on("/", systemSetup); // The client connected with no arguments e.g. http:192.160.0.40/
  97.   server.on("/TempHumi", display_temp_and_humidity);
  98.   server.on("/TempDewp", display_temp_and_dewpoint);
  99.   server.on("/Dialview", display_dial);
  100.   server.on("/AScale",   auto_scale);
  101.   server.on("/AUpdate",  auto_update);
  102.   server.on("/Setup",    systemSetup);
  103.   server.on("/Help",     help);
  104.   server.on("/MaxT_U",   max_temp_up);
  105.   server.on("/MaxT_D",   max_temp_down);
  106.   server.on("/MinT_U",   min_temp_up);
  107.   server.on("/MinT_D",   min_temp_down);
  108.   server.on("/LogT_U",   logtime_up);
  109.   server.on("/LogT_D",   logtime_down);
  110.   if (SD_present) {
  111.     server.on("/SDview",  SD_view);
  112.     server.on("/SDerase", SD_erase);
  113.     server.on("/SDstats", SD_stats);
  114.   }
  115.   configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov");  // Start time server
  116.   index_ptr    = 0;     // The array pointer that varies from 0 to table_size  
  117.   log_count    = 0;     // Keeps a count of readings taken
  118.   AScale    = false;    // Google charts can AScale axis, this switches the function on/off
  119.   max_temp     = 30;    // Maximum displayed temperature as default
  120.   min_temp     = -10;   // Minimum displayed temperature as default
  121.   auto_smooth  = false; // If true, transitions of more than 10% between readings are smoothed out, so a reading followed by another that is 10% higher or lower is averaged
  122.   AUpdate   = true;     // Used to prevent a command from continually auto-updating, for example increase temp-scale would increase every 30-secs if not prevented from doing so.
  123.   lastcall     = "temp_humi";      // To determine what requested the AScale change
  124.   log_interval = log_time_unit*20; // inter-log time interval, default is 5-minutes between readings, 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr
  125.   timer_cnt    = log_interval + 1; // To trigger first table update, essential
  126.   update_log_time();           // Update the log_time
  127.   log_delete_approved = false; // Used to prevent accidental deletion of card contents, requires two approvals
  128.   reset_array();               // Clear storage array before use
  129.   prefill_array();             // Load old data from SD-Card back into display and readings array
  130.   //Serial.println(system_get_free_heap_size()); // diagnostic print to check for available RAM
  131.   time_t now = time(nullptr);
  132.   delay(2000); // Wait for time to start
  133.   //Serial.println(time(&now)); // Unix time epoch
  134.   Serial.print(F("Logging started at: ")); Serial.println(calcDateTime(time(&now)));
  135.   /*you can also obtain time and date like this
  136.   struct tm *now_tm;
  137.   int hour,min,second,day,month,year;
  138.   now = time(NULL);
  139.   now_tm = localtime(&now);
  140.   hour = now_tm->tm_hour;
  141.   min  = now_tm->tm_min;
  142.   second = now_tm->tm_sec;
  143.   day    = now_tm->tm_mday;
  144.   month  = now_tm->tm_mon;
  145.   year   = now_tm->tm_year + 1900;
  146.   Serial.print(hour);Serial.print(":");Serial.print(min);Serial.print(":");Serial.println(second);
  147.   Serial.print(day);Serial.print("/");Serial.print(month);Serial.print("/");Serial.println(year);
  148.   */
  149. }
  150.  
  151. void loop() {
  152.   server.handleClient();
  153.   sensor_t sensor;
  154.   sensors_event_t event;
  155.   dht.temperature().getEvent(&event);
  156.   if (isnan(event.temperature))       Serial.println("Error reading temperature!"); else dht_temp = event.temperature*10;
  157.   dht.humidity().getEvent(&event);
  158.   if (isnan(event.relative_humidity)) Serial.println("Error reading humidity!");    else dht_humi = event.relative_humidity*10;
  159.  
  160.   time_t now = time(nullptr);
  161.   time_now = String(ctime(&now)).substring(0,24); // Remove unwanted characters
  162.   if (time_now != "Thu Jan 01 00:00:00 1970" and timer_cnt >= log_interval) { // If time is not yet set, returns 'Thu Jan 01 00:00:00 1970') so wait.
  163.     timer_cnt = 0;  // log_interval values are 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr
  164.     log_count += 1; // Increase logging event count
  165.     sensor_data[index_ptr].lcnt  = log_count;  // Record current log number, time, temp and humidity readings
  166.     sensor_data[index_ptr].temp  = dht_temp;
  167.     sensor_data[index_ptr].humi  = dht_humi;
  168.     sensor_data[index_ptr].ltime = calcDateTime(time(&now)); // time stamp of reading 'dd/mm/yy hh:mm:ss'
  169.     if (SD_present){ // If the SD-Card is present and board fitted then append the next reading to the log file called 'datalog.txt'
  170.       File dataFile = SD.open("datalog.txt", FILE_WRITE);
  171.       if (dataFile) { // if the file is available, write to it
  172.         dataFile.println(((log_count<10)?"0":"")+String(log_count)+char(9)+String(dht_temp/10,2)+char(9)+String(dht_humi/10,2)+char(9)+calcDateTime(time(&now))); // TAB delimited
  173.       }
  174.       dataFile.close();
  175.     }
  176.     index_ptr += 1; // Increment data record pointer
  177.     if (index_ptr > table_size) { // if number of readings exceeds max_readings (e.g. 100) then shift all array data to the left to effectively scroll the display left
  178.       index_ptr = table_size;
  179.       for (int i = 0; i < table_size; i++) { // If data table is full, scroll all readings to the left in graphical terms, then add new reading to the end
  180.         sensor_data[i].lcnt  = sensor_data[i+1].lcnt;
  181.         sensor_data[i].temp  = sensor_data[i+1].temp;
  182.         sensor_data[i].humi  = sensor_data[i+1].humi;
  183.         sensor_data[i].ltime = sensor_data[i+1].ltime;
  184.       }
  185.       sensor_data[table_size].lcnt  = log_count;
  186.       sensor_data[table_size].temp  = dht_temp;
  187.       sensor_data[table_size].humi  = dht_humi;
  188.       sensor_data[table_size].ltime = calcDateTime(time(&now));
  189.     }
  190.   }
  191.   timer_cnt += 1; // Readings set by value of log_interval each 40 = 1min
  192.   delay(498);     // Delay before next check for a client, adjust for 1-sec repeat interval. Temperature readings take some time to complete.
  193.   //Serial.println(millis());
  194. }
  195.  
  196. void prefill_array(){ // After power-down or restart and if the SD-Card has readings, load them back in
  197.   if (SD_present){
  198.     File dataFile = SD.open("datalog.txt", FILE_READ);
  199.     while (dataFile.available()) { // if the file is available, read from it
  200.       int read_ahead = dataFile.parseInt(); // Sometimes at the end of file, NULL data is returned, this tests for that
  201.       if (read_ahead != 0) { // Probably wasn't null data to use it, but first data element could have been zero and there is never a record 0!
  202.         sensor_data[index_ptr].lcnt  = read_ahead ;
  203.         sensor_data[index_ptr].temp  = dataFile.parseFloat()*10;
  204.         sensor_data[index_ptr].humi  = dataFile.parseFloat()*10;
  205.         sensor_data[index_ptr].ltime = dataFile.readStringUntil('\n');
  206.         index_ptr += 1;
  207.         log_count += 1;
  208.       }
  209.       if (index_ptr > table_size) {
  210.         for (int i = 0; i < table_size; i++) {
  211.            sensor_data[i].lcnt  = sensor_data[i+1].lcnt;
  212.            sensor_data[i].temp  = sensor_data[i+1].temp;
  213.            sensor_data[i].humi  = sensor_data[i+1].humi;
  214.            sensor_data[i].ltime = sensor_data[i+1].ltime;
  215.         }
  216.         index_ptr = table_size;
  217.       }
  218.     }
  219.     dataFile.close();
  220.     if (auto_smooth) { // During restarts there can be a difference in readings, giving a spike in the graph, this smooths that out, off by default though
  221.       // At this point the array holds data from the SD-Card, but sometimes during outage and resume, reading discontinuitie occur, so try to correct those.
  222.       float last_temp,last_humi;
  223.       for (int i = 1; i < table_size; i++) {
  224.         last_temp = sensor_data[i].temp;
  225.         last_humi = sensor_data[i].humi;
  226.         // Correct next reading if it is more than 10% different from last values
  227.         if ((sensor_data[i+1].temp > (last_temp * 1.1)) || (sensor_data[i+1].temp < (last_temp * 1.1))) sensor_data[i+1].temp = (sensor_data[i+1].temp+last_temp)/2; // +/-1% different then use last value
  228.         if ((sensor_data[i+1].humi > (last_humi * 1.1)) || (sensor_data[i+1].humi < (last_humi * 1.1))) sensor_data[i+1].humi = (sensor_data[i+1].humi+last_humi)/2;
  229.       }
  230.     }
  231.   }
  232. }
  233.  
  234. void display_temp_and_humidity() { // Processes a clients request for a graph of the data
  235.   // See google charts api for more details. To load the APIs, include the following script in the header of your web page.
  236.   // <script type="text/javascript" src="https://www.google.com/jsapi"></script>
  237.   // To autoload APIs manually, you need to specify the list of APIs to load in the initial <script> tag, rather than in a separate google.load call for each API. For instance, the object declaration to auto-load version 1.0 of the Search API (English language) and the local search element, would look like: {
  238.   // This would be compressed to: {"modules":[{"name":"search","version":"1.0","language":"en"},{"name":"elements","version":"1.0","packages":["
  239.   // See https://developers.google.com/chart/interactive/docs/basic_load_libs
  240.   log_delete_approved = false; // Prevent accidental SD-Card deletion
  241.   webpage = ""; // don't delete this command, it ensures the server works reliably!
  242.   append_page_header();
  243.   //https://developers.google.com/loader/ // https://developers.google.com/chart/interactive/docs/basic_load_libs
  244.   // https://developers.google.com/chart/interactive/docs/basic_preparing_data
  245.   // https://developers.google.com/chart/interactive/docs/reference#google.visualization.arraytodatatable and See appendix-A
  246.   // data format is: [field-name,field-name,field-name] then [data,data,data], e.g. [12, 20.5, 70.3]
  247.   webpage += "<script type=\"text/javascript\" src=\"https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['corechart']}]}\"></script>";
  248.   webpage += "<script type=\"text/javascript\"> google.setOnLoadCallback(drawChart);";
  249.   webpage += "function drawChart() {";
  250.    webpage += "var data = google.visualization.arrayToDataTable(";
  251.    webpage += "[['Reading','Temperature','Humidity'],";    
  252.    for (int i = 0; i <= index_ptr; i=i+2) {
  253.      webpage += "[" + String(i) + "," + String(float(sensor_data[i].temp)/10,1) + "," + String(float(sensor_data[i].humi)/1000,2) + "],";
  254.    }
  255.    webpage += "]);";
  256. //-----------------------------------
  257.    webpage += "var options = {";
  258.     webpage += "title:'DHT11 Temperature & Humidity Readings',titleTextStyle:{fontName:'Arial', fontSize:20, color: 'Maroon'},";
  259.     webpage += "legend:{position:'bottom'},colors:['red','blue'],backgroundColor:'#F7F2Fd',chartArea:{width:'85%',height:'65%'},";
  260.     webpage += "hAxis:{titleTextStyle:{color:'Purple',bold:true,fontSize:16},showTextEvery:1,title:'Sensor Readings for last: " + log_time + "'},";
  261.     //minorGridlines:{units:{hours:{format:['hh:mm:ss a','ha']},minutes:{format:['HH:mm a Z', ':mm']}}  to display  x-axis in time units
  262.     webpage += "vAxes:";
  263.     if (AScale) {
  264.       webpage += "{0:{viewWindowMode:'explicit',gridlines:{color:'black'}, title:'Temperature Deg-C',format:'##.##'},";
  265.       webpage += " 1:{gridlines:{color:'transparent'},viewWindow:{min:0,max:1},title:'Humidity %',format:'##%'},},";
  266.     }
  267.     else {
  268.       webpage += "{0:{viewWindowMode:'explicit',viewWindow:{min:"+String(min_temp)+",max:"+String(max_temp)+"},gridlines:{color:'black'},title:'Temperature Deg-C',format:'##.##'},";
  269.       webpage += " 1:{gridlines:{color:'transparent'},viewWindow:{min:0,max:1},title:'Humidity %',format:'##%'},},";
  270.     }
  271.     webpage += "series:{0:{targetAxisIndex:0},1:{targetAxisIndex:1},},curveType:'none'};";
  272.     webpage += "var chart = new google.visualization.LineChart(document.getElementById('line_chart'));chart.draw(data, options);";
  273.   webpage += "}";
  274.   webpage += "</script>";
  275.   //webpage += "<meta name='viewport' content='width=device-width, initial-scale=1.0, user-scalable=yes'>";
  276.   webpage += "<div id=\"line_chart\" style=\"width:1020px; height:500px\"></div>";
  277. //-----------------------------------
  278.   append_page_footer();
  279.   server.send(200, "text/html", webpage);
  280.   webpage = "";
  281.   lastcall = "temp_humi";
  282. }
  283.  
  284. void display_temp_and_dewpoint() { // Processes a clients request for a graph of the data
  285.   float dew_point;
  286.   // See google charts api for more details. To load the APIs, include the following script in the header of your web page.
  287.   // <script type="text/javascript" src="https://www.google.com/jsapi"></script>
  288.   // To autoload APIs manually, you need to specify the list of APIs to load in the initial <script> tag, rather than in a separate google.load call for each API. For instance, the object declaration to auto-load version 1.0 of the Search API (English language) and the local search element, would look like: {
  289.   // This would be compressed to: {"modules":[{"name":"search","version":"1.0","language":"en"},{"name":"elements","version":"1.0","packages":["
  290.   // See https://developers.google.com/chart/interactive/docs/basic_load_libs
  291.   log_delete_approved = false; // PRevent accidental SD-Card deletion
  292.   webpage = ""; // don't delete this command, it ensures the server works reliably!
  293.   append_page_header();
  294.   // https://developers.google.com/loader/ // https://developers.google.com/chart/interactive/docs/basic_load_libs
  295.   // https://developers.google.com/chart/interactive/docs/basic_preparing_data
  296.   // https://developers.google.com/chart/interactive/docs/reference#google.visualization.arraytodatatable and See appendix-A
  297.   // data format is: [field-name,field-name,field-name] then [data,data,data], e.g. [12, 20.5, 70.3]
  298.   webpage += "<script type=\"text/javascript\" src=\"https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['corechart']}]}/\"></script>";
  299.   webpage += "<script type=\"text/javascript\"> google.setOnLoadCallback(drawChart);";
  300.   webpage += "function drawChart() {";
  301.    webpage += "var data = google.visualization.arrayToDataTable(";
  302.    webpage += "[['Reading','Temperature','Dew Point'],";    
  303.    for (int i = 0; i <= index_ptr; i=i+2) {
  304.      if (isnan(Calc_DewPoint(sensor_data[i].temp/10,sensor_data[i].humi/10)) )dew_point = 0; else dew_point = Calc_DewPoint(sensor_data[i].temp/10,sensor_data[i].humi/10);
  305.      webpage += "[" + String(i) + "," + String(float(sensor_data[i].temp)/10,1) + "," + String(dew_point,1) + "],";
  306.    }
  307.    webpage += "]);";
  308. //-----------------------------------
  309.    webpage += "var options = {";
  310.     webpage += "title:'DHT11 Temperature and Dew Point Readings',titleTextStyle:{fontName:'Arial', fontSize:20, color: 'Maroon'},";
  311.     webpage += "legend:{position:'bottom'},colors:['red','orange'],backgroundColor:'#F7F2Fd',chartArea:{width:'85%',height:'65%'},";
  312.     webpage += "hAxis:{titleTextStyle:{color:'Purple',bold:true,fontSize:16},showTextEvery:1,title:'Sensor Readings for last: " + log_time + "'},";
  313.     //minorGridlines:{units:{hours:{format:['hh:mm:ss a','ha']},minutes:{format:['HH:mm a Z', ':mm']}}  to display  x-axis in time units
  314.     webpage += "vAxes:";
  315.     if (AScale)
  316.     webpage += "{0:{viewWindowMode:'explicit',gridlines:{color:'black'}, title:'Temperature Deg-C',format:'##.##'},},";
  317.     else
  318.     webpage += "{0:{viewWindowMode:'explicit',viewWindow:{min:"+String(min_temp)+",max:"+String(max_temp)+"},gridlines:{color:'black'},title:'Temperature Deg-C',format:'##.##'},},";
  319.     webpage += "series:{0:{targetAxisIndex:0},1:{targetAxisIndex:0},},curveType:'none'};";
  320.     webpage += "var chart = new google.visualization.LineChart(document.getElementById('line_chart'));chart.draw(data, options);";
  321.   webpage += "}";
  322.   webpage += "</script>";
  323.   //webpage += "<meta name='viewport' content='width=device-width,initial-scale=1.0,user-scalable=yes'>";
  324.   webpage += "<div id=\"line_chart\" style=\"width:1020px; height:500px\"></div>";
  325. //-----------------------------------
  326.   append_page_footer();
  327.   server.send(200, "text/html", webpage);
  328.   webpage  = "";
  329.   lastcall = "temp_dewp";
  330. }
  331.  
  332. void display_dial (){ // Processes a clients request for a dial-view of the data
  333.   log_delete_approved = false; // PRevent accidental SD-Card deletion
  334.   webpage = ""; // don't delete this command, it ensures the server works reliably!
  335.   append_page_header();
  336.   webpage += "<script type=\"text/javascript\" src=\"https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['gauge']}]}\"></script>";
  337.   webpage += "<script type=\"text/javascript\">";
  338.   webpage += "var temp=" + String(dht_temp/10,2) + ",humi=" + String(dht_humi/10,1) + ";";
  339.   // https://developers.google.com/chart/interactive/docs/gallery/gauge
  340.   webpage += "google.load('visualization','1',{packages: ['gauge']});";
  341.   webpage += "google.setOnLoadCallback(drawgaugetemp);";
  342.   webpage += "google.setOnLoadCallback(drawgaugehumi);";
  343.   webpage += "var gaugetempOptions={min:-20,max:50,yellowFrom:-20,yellowTo:0,greenFrom:0,greenTo:30,redFrom:30,redTo:50,minorTicks:10,majorTicks:['-20','-10','0','10','20','30','40','50']};";
  344.   webpage += "var gaugehumiOptions={min:0,max:100,yellowFrom:0,yellowTo:25,greenFrom:25,greenTo:75,redFrom:75,redTo:100,minorTicks:10,majorTicks:['0','10','20','30','40','50','60','70','80','90','100']};";
  345.   webpage += "var gaugetemp,gaugehumi;";
  346.   webpage += "function drawgaugetemp() {gaugetempData = new google.visualization.DataTable();";
  347.   webpage += "gaugetempData.addColumn('number','deg-C');"; // 176 is Deg-symbol, there are problems displaying the deg-symbol in google charts
  348.   webpage += "gaugetempData.addRows(1);gaugetempData.setCell(0,0,temp);";
  349.   webpage += "gaugetemp = new google.visualization.Gauge(document.getElementById('gaugetemp_div'));";
  350.   webpage += "gaugetemp.draw(gaugetempData, gaugetempOptions);}";
  351.   webpage += "function drawgaugehumi() {gaugehumiData = new google.visualization.DataTable();gaugehumiData.addColumn('number','%');";
  352.   webpage += "gaugehumiData.addRows(1);gaugehumiData.setCell(0,0,humi);";
  353.   webpage += "gaugehumi = new google.visualization.Gauge(document.getElementById('gaugehumi_div'));";
  354.   webpage += "gaugehumi.draw(gaugehumiData,gaugehumiOptions);};";
  355.   webpage += "</script>";
  356.   webpage += "<meta name=\"viewport\" content=\"width=1020px, initial-scale=1.0, user-scalable=yes\">";
  357.   webpage += "<div id='gaugetemp_div' style='width:350px; height:350px;'></div>";
  358.   webpage += "<div id='gaugehumi_div' style='width:350px; height:350px;'></div>";
  359.   append_page_footer();
  360.   server.send(200, "text/html", webpage);
  361.   webpage = "";
  362.   lastcall = "dial";
  363. }
  364.  
  365. float Calc_DewPoint(float temp, float humi) {
  366.   return 243.04*(log(humi/100)+((17.625*temp)/(243.04+temp)))/(17.625-log(humi/100)-((17.625*temp)/(243.04+temp)));
  367. }
  368.  
  369. void reset_array() {
  370.   for (int i = 0; i <= table_size; i++) {
  371.     sensor_data[i].lcnt  = 0;
  372.     sensor_data[i].temp  = 0;
  373.     sensor_data[i].humi  = 0;
  374.     sensor_data[i].ltime = "";
  375.   }
  376. }
  377.  
  378. // After the data has been displayed, select and copy it, then open Excel and Paste-Special and choose Text, then select and insert graph to view
  379. void SD_view() {
  380.   if (SD_present) {
  381.     File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card
  382.     if (dataFile) {
  383.       if (dataFile.available()) { // If data is available and present
  384.         String dataType = "application/octet-stream";
  385.         if (server.streamFile(dataFile, dataType) != dataFile.size()) {Serial.print(F("Sent less data than expected!")); }
  386.       }
  387.     }
  388.     dataFile.close(); // close the file:
  389.   }
  390.   webpage = "";
  391. }  
  392.  
  393. void SD_erase() { // Erase the datalog file
  394.   webpage = ""; // don't delete this command, it ensures the server works reliably!
  395.   append_page_header();
  396.   if (AUpdate) webpage += "<meta http-equiv='refresh' content='30'>"; // 30-sec refresh time and test is needed to stop auto updates repeating some commands
  397.   if (log_delete_approved) {
  398.     if (SD_present) {
  399.       File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card
  400.       if (dataFile) if (SD.remove("datalog.txt")) Serial.println(F("File deleted successfully"));
  401.       webpage += "<h3>Log file 'datalog.txt' has been erased</h3>";
  402.       log_count = 0;
  403.       index_ptr = 0;
  404.       timer_cnt = 2000; // To trigger first table update, essential
  405.       log_delete_approved = false; // Re-enable sd card deletion
  406.     }
  407.   }
  408.   else {
  409.     log_delete_approved = true;
  410.     webpage += "<h3>Log file erasing is enabled, repeat this option to erase the log. Graph or Dial Views disable erasing again</h3>";
  411.   }
  412.   append_page_footer();
  413.   server.send(200, "text/html", webpage);
  414.   webpage = "";
  415. }
  416.  
  417. void SD_stats(){  // Display file size of the datalog file
  418.   webpage = ""; // don't delete this command, it ensures the server works reliably!
  419.   append_page_header();
  420.   File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card
  421.   webpage += "<h3>Data Log file size = "+String(dataFile.size())+"-Bytes</h3>";  
  422.   dataFile.close();
  423.   append_page_footer();
  424.   server.send(200, "text/html", webpage);
  425.   webpage = "";
  426. }
  427.  
  428. void auto_scale () { // Google Charts can auto-scale graph axis, this turns it on/off
  429.   if (AScale) AScale = false; else AScale = true;
  430.   if (lastcall == "temp_humi") display_temp_and_humidity();
  431.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  432.   if (lastcall == "dial")      display_dial();
  433. }
  434.  
  435. void auto_update () { // Google Charts can auto-scale graph axis, this turns it on/off
  436.   if (AUpdate) AUpdate = false; else AUpdate = true;
  437.   if (lastcall == "temp_humi") display_temp_and_humidity();
  438.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  439.   if (lastcall == "dial")      display_dial();
  440. }
  441.  
  442. void max_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off
  443.   max_temp += 1;
  444.   if (max_temp >60) max_temp = 60;
  445.   if (lastcall == "temp_humi") display_temp_and_humidity();
  446.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  447.   if (lastcall == "dial")      display_dial();
  448. }
  449.  
  450. void max_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off
  451.   max_temp -= 1;
  452.   if (max_temp <0) max_temp = 0;
  453.   if (lastcall == "temp_humi") display_temp_and_humidity();
  454.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  455.   if (lastcall == "dial")      display_dial();
  456. }
  457.  
  458. void min_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off
  459.   min_temp += 1;
  460.   if (lastcall == "temp_humi") display_temp_and_humidity();
  461.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  462.   if (lastcall == "dial")      display_dial();
  463. }
  464.  
  465. void min_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off
  466.   min_temp -= 1;
  467.   if (min_temp < -60) min_temp = -60;
  468.   if (lastcall == "temp_humi") display_temp_and_humidity();
  469.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  470.   if (lastcall == "dial")      display_dial();
  471. }
  472.  
  473. void logtime_down () {  // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function
  474.   log_interval -= log_time_unit;
  475.   if (log_interval < log_time_unit) log_interval = log_time_unit;
  476.   update_log_time();
  477.   if (lastcall == "temp_humi") display_temp_and_humidity();
  478.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  479.   if (lastcall == "dial")      display_dial();
  480. }
  481.  
  482. void logtime_up () {  // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function
  483.   log_interval += log_time_unit;
  484.   update_log_time();
  485.   if (lastcall == "temp_humi") display_temp_and_humidity();
  486.   if (lastcall == "temp_dewp") display_temp_and_dewpoint();
  487.   if (lastcall == "dial")      display_dial();
  488. }
  489.  
  490. void update_log_time() {
  491.   float log_hrs;
  492.   log_hrs = table_size*log_interval/time_reference;
  493.   log_hrs = log_hrs / 60; // Should not be needed, but compiler cant' calcuate the result in-line!
  494.   float log_mins = (log_hrs - int(log_hrs))*60;
  495.   log_time = String(int(log_hrs))+":"+((log_mins<10)?"0"+String(int(log_mins)):String(int(log_mins)))+" Hrs  ("+String(log_interval)+")-secs between log entries";
  496.   log_time += ", Free-mem:("+String(system_get_free_heap_size())+")";
  497. }
  498.  
  499. void systemSetup() {
  500.   webpage = ""; // don't delete this command, it ensures the server works reliably!
  501.   append_page_header();
  502.   String IPaddress = WiFi.localIP().toString();
  503.   webpage += "<h3>System Setup, if required enter values then choose Graph or Dial</h3>";
  504.   webpage += "<meta http-equiv=\"refresh\" content=\"20\"; URL=http://" + IPaddress + "/Graphview>";
  505.   webpage += "<form action=\"http://"+IPaddress+"\" method=\"POST\">";
  506.   webpage += "Maximum Temperature on Graph axis (currently = "+String(max_temp)+char(176)+"C<br>";
  507.   webpage += "<input type=\"text\" name=\"max_temp_in\" value=\"30\"><br>";
  508.   webpage += "Minimum Temperature on Graph axis (currently = "+String(min_temp)+char(176)+"C<br>";
  509.   webpage += "<input type=\"text\" name=\"min_temp_in\" value=\"-10\"><br>";
  510.   webpage += "Logging Interval (currently = "+String(log_interval)+"-Secs)<br>";
  511.   webpage += "<input type=\"text\" name=\"log_interval_in\" value=\"20\"><br>";
  512.   webpage += "Auto-scale Graph (currently = "+String(AScale?"ON":"OFF")+"<br>";
  513.   webpage += "<input type=\"text\" name=\"auto_scale\" value=\"OFF\"><br>";
  514.   webpage += "Auto-update Graph (currently = "+String(AUpdate?"ON":"OFF")+"<br>";
  515.   webpage += "<input type=\"text\" name=\"auto_update\" value=\"ON\"><br>";
  516.   webpage += "<input type=\"submit\" value=\"Enter\"><br><br>";
  517.   webpage += "</form></body>";
  518.   append_page_footer();
  519.   server.send(200, "text/html", webpage); // Send a response to the client asking for input
  520.   if (server.args() > 0 ) { // Arguments were received
  521.     for ( uint8_t i = 0; i < server.args(); i++ ) {
  522.       String Argument_Name   = server.argName(i);
  523.       String client_response = server.arg(i);
  524.       if (Argument_Name == "max_temp_in") {
  525.         if (client_response.toInt()) max_temp = client_response.toInt(); else max_temp = 30;
  526.       }
  527.       if (Argument_Name == "min_temp_in") {
  528.         if (client_response.toInt() == 0) min_temp = 0; else min_temp = client_response.toInt();
  529.       }
  530.       if (Argument_Name == "log_interval_in") {
  531.         if (client_response.toInt()) log_interval = client_response.toInt(); else log_interval = 300;
  532.         log_interval = client_response.toInt()*log_time_unit;
  533.       }
  534.       if (Argument_Name == "auto_scale") {
  535.         if (client_response == "ON") AScale = true; else AScale = false;
  536.       }
  537.       if (Argument_Name == "auto_update") {
  538.         if (client_response == "ON") AUpdate = true; else AUpdate = false;
  539.       }
  540.     }
  541.   }
  542.   webpage = "";
  543.   update_log_time();
  544. }
  545.  
  546. void append_page_header() {
  547.   webpage  = "<!DOCTYPE html><html><head>";
  548.   if (AUpdate) webpage += "<meta http-equiv='refresh' content='30'>"; // 30-sec refresh time, test needed to prevent auto updates repeating some commands
  549.   webpage += "<title>DHT11 Sensor Readings</title><style>";
  550.   webpage += "body {width:1020px;margin:0 auto;font-family:arial;font-size:14px;text-align:center;color:blue;background-color:#F7F2Fd;}";
  551.   webpage += "</style></head><body><h1>Autonomous Graphing Data Logger " + version + "</h1>";
  552. }
  553.  
  554.  
  555. void append_page_footer(){ // Saves repeating many lines of code for HTML page footers
  556.   webpage += "<head><style>ul{list-style-type:none;margin:0;padding:0;overflow:hidden;background-color:#d8d8d8;font-size:14px;}";
  557.   webpage += "li{float:left;border-right:1px solid #bbb;}last-child {border-right: none;}";
  558.   webpage += "li a{display: block;padding:3px 15px;text-decoration:none;}";
  559.   webpage += "li a:hover{background-color:#FFFFFF;}";
  560.   webpage += "section {font-size:14px;}";
  561.   webpage += "p {background-color:#E3D1E2;}";
  562.   webpage += "h1{background-color:#d8d8d8;}";
  563.   webpage += "h3{color:orange;font-size:24px;}";
  564.   webpage += "</style>";
  565.   webpage += "<ul><li><a class=\"active\" <a href=\"/TempHumi\">Temperature-Humidity</a></li>";
  566.   webpage += "<li><a href=\"/TempDewp\">Temperature-Dewpoint</a></li>";
  567.   webpage += "<li><a href=\"/Dialview\">Dial</a></li>";
  568.   webpage += "<li><a href=\"/MaxT_U\">Max&degC&uArr;</a></li>";
  569.   webpage += "<li><a href=\"/MaxT_D\">Max&degC&dArr;</a></li>";
  570.   webpage += "<li><a href=\"/MinT_U\">Min&degC&uArr;</a></li>";
  571.   webpage += "<li><a href=\"/MinT_D\">Min&degC&dArr;</a></li>";
  572.   webpage += "<li><a href=\"/LogT_U\">Logging&dArr;</a></li>";
  573.   webpage += "<li><a href=\"/LogT_D\">Logging&uArr;</a></li>";
  574.   webpage += "<li><a href=\"/AScale\">AutoScale("  +String((AScale? "ON":"OFF"))+")</a></li>";
  575.   webpage += "<li><a href=\"/AUpdate\">AutoUpdate("+String((AUpdate?"ON":"OFF"))+")</a></li>";
  576.   webpage += "<li><a href=\"/Setup\">Setup</a></li>";
  577.   webpage += "<li><a href=\"/Help\">Help</a></li>";
  578.   if (SD_present) {
  579.     webpage += "<li><a href=\"/SDstats\">Log Size</a></li>";
  580.     webpage += "<li><a href=\"/SDview\">View Log</a></li>";
  581.     webpage += "<li><a href=\"/SDerase\">Erase Log</a></li>";
  582.   }
  583.   webpage += "</ul>";
  584.   webpage += "<p>&copy;"+String(char(byte(0x40>>1)))+String(char(byte(0x88>>1)))+String(char(byte(0x5c>>1)))+String(char(byte(0x98>>1)))+String(char(byte(0x5c>>1)));
  585.   webpage += String(char((0x84>>1)))+String(char(byte(0xd2>>1)))+String(char(0xe4>>1))+String(char(0xc8>>1))+String(char(byte(0x40>>1)));
  586.   webpage += String(char(byte(0x64/2)))+String(char(byte(0x60>>1)))+String(char(byte(0x62>>1)))+String(char(0x6c>>1))+"</p>";
  587.   webpage += "</body></html>";
  588. }
  589.  
  590. String calcDateTime(int epoch){
  591.   int seconds, minutes, hours, dayOfWeek, current_day, current_month, current_year;
  592.   seconds      = epoch;
  593.   minutes      = seconds / 60; // calculate minutes
  594.   seconds     -= minutes * 60; // calculate seconds
  595.   hours        = minutes / 60; // calculate hours
  596.   minutes     -= hours   * 60;
  597.   current_day  = hours   / 24; // calculate days
  598.   hours       -= current_day * 24;
  599.   current_year = 1970;         // Unix time starts in 1970
  600.   dayOfWeek    = 4;            // on a Thursday
  601.   while(1){
  602.     bool     leapYear   = (current_year % 4 == 0 && (current_year % 100 != 0 || current_year % 400 == 0));
  603.     uint16_t daysInYear = leapYear ? 366 : 365;
  604.     if (current_day >= daysInYear) {
  605.       dayOfWeek += leapYear ? 2 : 1;
  606.       current_day   -= daysInYear;
  607.       if (dayOfWeek >= 7) dayOfWeek -= 7;
  608.       ++current_year;
  609.     }
  610.     else
  611.     {
  612.       dayOfWeek  += current_day;
  613.       dayOfWeek  %= 7;
  614.       /* calculate the month and day */
  615.       static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  616.       for(current_month = 0; current_month < 12; ++current_month) {
  617.         uint8_t dim = daysInMonth[current_month];
  618.         if (current_month == 1 && leapYear) ++dim; // add a day to February if a leap year
  619.         if (current_day >= dim) current_day -= dim;
  620.         else break;
  621.       }
  622.       break;
  623.     }
  624.   }
  625.   current_month += 1; // Months are 0..11 and returned format is dd/mm/ccyy hh:mm:ss
  626.   current_day   += 1;
  627.   String date_time = String(current_day) + "/" + String(current_month) + "/" + String(current_year) + " ";
  628.   date_time += ((hours   < 10) ? "0" + String(hours): String(hours))     + ":";
  629.   date_time += ((minutes < 10) ? "0" + String(minutes): String(minutes)) + ":";
  630.   date_time += ((seconds < 10) ? "0" + String(seconds): String(seconds));
  631.   return date_time;
  632. }
  633.  
  634. void help() {
  635.   webpage = ""; // don't delete this command, it ensures the server works reliably!
  636.   append_page_header();
  637.   webpage += "<section>";
  638.   webpage += "Temperature&Humidity - a graph of temperature and humidity<br>";
  639.   webpage += "Temperature&Dewpoint - a graph of temperature and dewpoint<br>";
  640.   webpage += "Dial - displays current temperature and humidity values<br>";
  641.   webpage += "Max&degC&uArr; - increase maximum y-axis by 1&degC;<br>";
  642.   webpage += "Max&degC&dArr; - decrease maximum y-axis by 1&degC;<br>";
  643.   webpage += "Min&degC&uArr; - increase minimum y-axis by 1&degC;<br>";
  644.   webpage += "Min&degC&dArr; - decrease minimum y-axis by 1&degC;<br>";
  645.   webpage += "Logging&dArr; - reduce logging speed with more time between log entries<br>";
  646.   webpage += "Logging&uArr; - increase logging speed with less time between log entries<br>";
  647.   webpage += "Auto-scale(ON/OFF) - toggle the graph Auto-scale ON/OFF<br>";
  648.   webpage += "Auto-update(ON/OFF) - toggle screen Auto-refresh ON/OFF<br>";
  649.   webpage += "Setup - allows some settings to be adjusted<br><br>";
  650.   webpage += "The following functions are enabled when an SD-Card reader is fitted:<br>";
  651.   webpage += "Log Size - display log file size in bytes<br>";
  652.   webpage += "View Log - stream log file contents to the screen, copy and paste into a spreadsheet using paste special, text<br>";
  653.   webpage += "Erase Log - erase log file, needs two approvals using this function. Any data display function resets the initial erase approval<br><br>";
  654.   webpage += "</section>";
  655.   append_page_footer();
  656.   server.send(200, "text/html", webpage);
  657.   webpage = "";
  658. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement