├── Card Wiring.jpg ├── ESP8266_BME280_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino ├── ESP8266_DHT11_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino ├── ESP8266_DHT22_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino ├── ESP8266_SHT30_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino ├── Licence.txt ├── README.md └── Sensor Wiring.jpg /Card Wiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/G6EJD/ESP8266-Autonomous-Graphing-Data-Logger/d91de050178bcd6abfe821701dc358dfb581aa43/Card Wiring.jpg -------------------------------------------------------------------------------- /ESP8266_BME280_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino: -------------------------------------------------------------------------------- 1 | /* ESP8266 plus WEMOS Bosch BME280 Sensor with a Temperature, Humidity and Pressure 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 16 | #include 17 | #include 18 | #include // https://github.com/tzapu/WiFiManager 19 | #include 20 | #include 21 | #include // Needs to be version 1.0.9 to work with ESP8266 22 | #include 23 | #include 24 | #include 25 | 26 | float bme_pressure, bme_temp, bme_humidity, heat_index, dew_point; 27 | 28 | Adafruit_BME280 bme; // Note Adafruit assumes an I2C adress of 0x77, my module uses 0x76, so change the address in adafuit_BME280 .h file like this: #define BME280_ADDRESS (0x76) 29 | 30 | String version = "v1.0"; // Version of this program 31 | WiFiClient client; 32 | 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 33 | // 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 34 | // connection request to http://your_network_ip_address:8266 to port 8266 and view your ESP server from anywhere. 35 | // 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 36 | 37 | uint16_t log_time_unit = 15; // default is 1-minute between readings, 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 38 | uint16_t time_reference = 60; // Time reference for calculating /log-time (nearly in secs) to convert to minutes 39 | uint16_t const table_size = 288; // 300 is the maximum for the available memory, 12 samples/hour * 24 * 1-day = 288 40 | int index_ptr,log_count,timer_cnt,log_interval,max_temp,min_temp,max_pres,min_pres; // Must be type int 41 | String webpage,time_now,log_time,lastcall; 42 | bool SD_present, AScale, auto_smooth, AUpdate, log_delete_approved; 43 | 44 | typedef struct { 45 | int lcnt; // Sequential log count 46 | String ltime; // Time reading taken 47 | sint16_t temp; // Temperature values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 48 | sint16_t humi; // Humidity values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 49 | sint16_t pres; // Pressure values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 50 | } record_type; 51 | 52 | record_type sensor_data[table_size+1]; // Define the data array 53 | 54 | void setup() { 55 | Serial.begin(115200); 56 | //WiFiManager intialisation. Once the following has completed there is no need to repeat the process on the current board 57 | WiFiManager wifiManager; 58 | // 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 59 | // so restart the ESP8266 and connect your PC to the wireless access point called 'ESP8266_AP' or whatever you call it below in "" 60 | // wifiManager.resetSettings(); // Command to be included if needed, then connect to http://192.168.4.1/ and follow instructions to make the WiFi connection 61 | // Set a timeout until configuration is turned off, useful to retry or go to sleep in n-seconds 62 | wifiManager.setTimeout(180); 63 | //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 64 | if(!wifiManager.autoConnect("ESP8266_AP")) { 65 | Serial.println(F("failed to connect and timeout occurred")); // Using print from Flash to save as much RAM as possible 66 | delay(3000); 67 | ESP.reset(); //reset and try again 68 | delay(5000); 69 | } 70 | // At this stage the WiFi manager will have successfully connected to a network, or if not will try again in 180-seconds 71 | //---------------------------------------------------------------------- 72 | Serial.println(F("WiFi connected..")); 73 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); 74 | server.begin(); Serial.println(F("Webserver started...")); // Start the webserver 75 | Serial.println("Use this URL to connect: http://"+WiFi.localIP().toString()+"/");// Print the IP address 76 | //---------------------------------------------------------------------- 77 | Serial.print(F("Initializing SD card...")); 78 | if (!SD.begin(D8)) { // see if the card is present and can be initialised. Wemos SD-Card CS uses D8 79 | Serial.println(F("Card failed, or not present")); 80 | SD_present = false; 81 | } else SD_present = true; 82 | if (SD_present) Serial.println(F("Card initialised.")); else Serial.println(F("Card not present, no SD Card data logging possible")); 83 | //---------------------------------------------------------------------- 84 | Wire.begin(D3,D4); 85 | if (!bme.begin()) { 86 | Serial.println(F("Could not find a valid BME280 sensor, check wiring!")); 87 | } 88 | server.on("/", systemSetup); // The client connected with no arguments e.g. http:192.160.0.40/ 89 | server.on("/TempHumi", display_temp_and_humidity); 90 | server.on("/TempDewp", display_temp_and_dewpoint); 91 | server.on("/Pressure", display_pressure); 92 | server.on("/Dialview", display_dial); 93 | server.on("/AScale", auto_scale); 94 | server.on("/AUpdate", auto_update); 95 | server.on("/Setup", systemSetup); 96 | server.on("/Help", help); 97 | server.on("/MaxT_U", max_temp_up); 98 | server.on("/MaxT_D", max_temp_down); 99 | server.on("/MinT_U", min_temp_up); 100 | server.on("/MinT_D", min_temp_down); 101 | server.on("/LogT_U", LogT_U); 102 | server.on("/LogT_D", LogT_D); 103 | if (SD_present) { 104 | server.on("/SDview", SD_view); 105 | server.on("/SDerase", SD_erase); 106 | server.on("/SDstats", SD_stats); 107 | } 108 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // Start time server 109 | index_ptr = 0; // The array pointer that varies from 0 to table_size 110 | log_count = 0; // Keeps a count of readings taken 111 | AScale = false; // Google charts can AScale axis, this switches the function on/off 112 | lastcall = "temp_humi"; // To determine what requested the AScale change 113 | max_temp = 30; // Maximum displayed temperature as default 114 | min_temp = -10; // Minimum displayed temperature as default 115 | max_pres = 1050; // Maximum displayed air pressure as default 116 | min_pres = 950; // Minimum displayed air pressure as default 117 | auto_smooth = false; // If true, transitions of more than 10% between readings are smoothed out 118 | AUpdate = true; // Used to prevent last commands from continually auto-updating 119 | log_interval = log_time_unit*20; // inter-log time interval, default 300-secs, so 288 readings x 300 = 86400 / 60 /60 = 24:00 hrs total on screen 120 | timer_cnt = log_interval; // To trigger first table update, essential 121 | update_log_time(); // Update the log_time 122 | log_delete_approved = false; // Used to prevent accidental deletion of card contents, requires two approvals 123 | reset_array(); // Clear storage array before use 124 | prefill_array(); // Load old data from SD-Card back into display and readings array 125 | //Serial.println(system_get_free_heap_size()); // diagnostic print to check for available RAM 126 | time_t now = time(nullptr); 127 | delay(2000); // Wait for time to start 128 | //Serial.println(time(&now)); // Unix time epoch 129 | Serial.print(F("Logging started at: ")); Serial.println(calcDateTime(time(&now))); 130 | /*you can also obtain time and date like this 131 | struct tm *now_tm; 132 | int hour,min,second,day,month,year; 133 | now = time(NULL); 134 | now_tm = localtime(&now); 135 | hour = now_tm->tm_hour; 136 | min = now_tm->tm_min; 137 | second = now_tm->tm_sec; 138 | day = now_tm->tm_mday; 139 | month = now_tm->tm_mon; 140 | year = now_tm->tm_year + 1900; 141 | Serial.print(hour);Serial.print(":");Serial.print(min);Serial.print(":");Serial.println(second); 142 | Serial.print(day);Serial.print("/");Serial.print(month);Serial.print("/");Serial.println(year); 143 | */ 144 | } 145 | 146 | void loop() { 147 | server.handleClient(); 148 | bme_temp = bme.readTemperature(); // No correction factor needed for this sensor 149 | bme_humidity = bme.readHumidity() + 1; // Plus a correction factor for this sensor 150 | bme_pressure = bme.readPressure()/100 + 2.7; // Plus a correction factor for this sensor 151 | float T = (bme_temp * 9 / 5) + 32; // Convert back to deg-F for the RH equation 152 | float RHx = bme_humidity; // Short form of RH for inclusion in the equation makes it easier to read 153 | heat_index = (-42.379+(2.04901523*T)+(10.14333127*RHx)-(0.22475541*T*RHx)-(0.00683783*sq(T))-(0.05481717*sq(RHx))+(0.00122874*sq(T)*RHx)+(0.00085282*T*sq(RHx))-(0.00000199*sq(T)*sq(RHx))-32)*5/9; 154 | if ((bme_temp <= 26.66) || (bme_humidity <= 40)) heat_index = bme_temp; // The convention is not to report heat Index when temperature is < 26.6 Deg-C or humidity < 40% 155 | dew_point = 243.04*(log(bme_humidity/100)+((17.625*bme_temp)/(243.04+bme_temp)))/(17.625-log(bme_humidity/100)-((17.625*bme_temp)/(243.04+bme_temp))); 156 | dew_point = Calc_DewPoint(bme_temp,bme_humidity); 157 | time_t now = time(nullptr); 158 | if (String(ctime(&now)).substring(0,24) != "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. 159 | timer_cnt = 0; // log_interval values are 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 160 | log_count += 1; // Increase logging event count 161 | sensor_data[index_ptr].lcnt = log_count; // Record current log number, time, temp and humidity readings 162 | sensor_data[index_ptr].temp = bme_temp*10; 163 | sensor_data[index_ptr].humi = bme_humidity*10; 164 | sensor_data[index_ptr].pres = bme_pressure*10; 165 | sensor_data[index_ptr].ltime = calcDateTime(time(&now)); // time stamp of reading 'dd/mm/yy hh:mm:ss' 166 | if (SD_present){ // If the SD-Card is present and board fitted then append the next reading to the log file called 'datalog.txt' 167 | File dataFile = SD.open("datalog.txt", FILE_WRITE); 168 | if (dataFile) { // if the file is available, write to it 169 | dataFile.println(((log_count<10)?"0":"")+ 170 | String(log_count)+char(9)+String(float(bme_temp),1)+char(9)+String(float(bme_humidity),1)+char(9)+ 171 | String(float(bme_pressure),1)+char(9)+calcDateTime(time(&now))); // TAB delimited 172 | } 173 | dataFile.close(); 174 | } 175 | index_ptr += 1; // Increment data record pointer 176 | 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 177 | index_ptr = table_size; 178 | 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 179 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 180 | sensor_data[i].temp = sensor_data[i+1].temp; 181 | sensor_data[i].humi = sensor_data[i+1].humi; 182 | sensor_data[i].pres = sensor_data[i+1].pres; 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 = bme_temp*10; 187 | sensor_data[table_size].humi = bme_humidity*10; 188 | sensor_data[table_size].pres = bme_pressure*10; 189 | sensor_data[table_size].ltime = calcDateTime(time(&now)); 190 | } 191 | } 192 | timer_cnt += 1; // Increments in units of log_time_unit, so if 15-secs and desired log interval is 90-secs timer_cnt will climb to 6 193 | delay(1000); // Delay before next check for a client 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].pres = dataFile.parseFloat()*10; 206 | sensor_data[index_ptr].ltime = dataFile.readStringUntil('\n'); 207 | index_ptr += 1; 208 | log_count += 1; 209 | } 210 | if (index_ptr > table_size) { 211 | for (int i = 0; i < table_size; i++) { 212 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 213 | sensor_data[i].temp = sensor_data[i+1].temp; 214 | sensor_data[i].humi = sensor_data[i+1].humi; 215 | sensor_data[i].pres = sensor_data[i+1].pres; 216 | sensor_data[i].ltime = sensor_data[i+1].ltime; 217 | } 218 | index_ptr = table_size; 219 | } 220 | } 221 | dataFile.close(); 222 | 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 223 | // 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. 224 | float last_temp,last_humi; 225 | for (int i = 1; i < table_size; i++) { 226 | last_temp = sensor_data[i].temp; 227 | last_humi = sensor_data[i].humi; 228 | // Correct next reading if it is more than 10% different from last values 229 | 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 230 | 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; 231 | } 232 | } 233 | } 234 | } 235 | 236 | void display_temp_and_humidity() { // Processes a clients request for a graph of the data 237 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 238 | // 239 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 247 | webpage += F(""; 277 | //webpage += ""; 278 | webpage += "
"; 279 | //----------------------------------- 280 | append_page_footer(); 281 | server.send(200, "text/html", webpage); 282 | webpage = ""; 283 | lastcall = "temp_humi"; 284 | } 285 | 286 | void display_temp_and_dewpoint() { // Processes a clients request for a graph of the data 287 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 288 | // 289 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 297 | webpage += F(""; 328 | //webpage += ""; 329 | webpage += "
"; 330 | //----------------------------------- 331 | append_page_footer(); 332 | server.send(200, "text/html", webpage); 333 | webpage = ""; 334 | lastcall = "temp_dewp"; 335 | } 336 | 337 | void display_pressure() { // Processes a clients request for a graph of the pressure 338 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 339 | // 340 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 348 | webpage += F(""; 376 | //webpage += ""; 377 | webpage += "
"; 378 | //----------------------------------- 379 | append_page_footer(); 380 | server.send(200, "text/html", webpage); 381 | webpage = ""; 382 | lastcall = "pressure"; 383 | } 384 | 385 | void display_dial(){ // Processes a clients request for a dial-view of the data 386 | log_delete_approved = false; // PRevent accidental SD-Card deletion 387 | webpage = ""; // don't delete this command, it ensures the server works reliably! 388 | append_page_header(); 389 | //############### New API call needed 390 | webpage += ""; 391 | webpage += ""; 423 | webpage += "
"; 424 | webpage += "
"; 425 | webpage += "
"; 426 | webpage += "
"; 427 | webpage += "
"; 428 | append_page_footer(); 429 | server.send(200, "text/html", webpage); 430 | webpage = ""; 431 | lastcall = "dial"; 432 | } 433 | 434 | float Calc_DewPoint(float temp, float humi) { 435 | return 243.04*(log(humi/100)+((17.625*temp)/(243.04+temp)))/(17.625-log(humi/100)-((17.625*temp)/(243.04+temp))); 436 | } 437 | 438 | void reset_array() { 439 | for (int i = 0; i <= table_size; i++) { 440 | sensor_data[i].lcnt = 0; 441 | sensor_data[i].temp = 0; 442 | sensor_data[i].humi = 0; 443 | sensor_data[i].pres = 0; 444 | sensor_data[i].ltime = ""; 445 | } 446 | } 447 | 448 | // 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 449 | void SD_view() { 450 | if (SD_present) { 451 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 452 | if (dataFile) { 453 | if (dataFile.available()) { // If data is available and present 454 | String dataType = "application/octet-stream"; 455 | if (server.streamFile(dataFile, dataType) != dataFile.size()) {Serial.print(F("Sent less data than expected!")); } 456 | } 457 | } 458 | dataFile.close(); // close the file: 459 | } 460 | } 461 | 462 | void SD_erase() { // Erase the datalog file 463 | webpage = ""; // don't delete this command, it ensures the server works reliably! 464 | append_page_header(); 465 | if (log_delete_approved) { 466 | if (SD_present) { 467 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 468 | if (dataFile) if (SD.remove("datalog.txt")) Serial.println(F("File deleted successfully")); 469 | webpage += "

Log file 'datalog.txt' has been erased

"; 470 | log_count = 0; 471 | index_ptr = 0; 472 | timer_cnt = 2000; // To trigger first table update, essential 473 | log_delete_approved = false; // Re-enable sd card deletion 474 | } 475 | } 476 | else { 477 | log_delete_approved = true; 478 | webpage += "

Log file erasing is enabled, repeat this option to erase the log. Graph or Dial Views disable erasing again

"; 479 | } 480 | append_page_footer(); 481 | server.send(200, "text/html", webpage); 482 | webpage = ""; 483 | } 484 | 485 | void SD_stats(){ // Display file size of the datalog file 486 | webpage = ""; // don't delete this command, it ensures the server works reliably! 487 | append_page_header(); 488 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 489 | webpage += "

Data Log file size = "+String(dataFile.size())+"-Bytes

"; 490 | dataFile.close(); 491 | append_page_footer(); 492 | server.send(200, "text/html", webpage); 493 | webpage = ""; 494 | } 495 | 496 | void auto_scale () { // Google Charts can auto-scale graph axis, this turns it on/off 497 | if (AScale) AScale = false; else AScale = true; 498 | if (lastcall == "temp_humi") display_temp_and_humidity(); 499 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 500 | if (lastcall == "pressure") display_pressure(); 501 | if (lastcall == "dial") display_dial(); 502 | } 503 | 504 | void auto_update () { // Auto-refresh of the screen, this turns it on/off 505 | if (AUpdate) AUpdate = false; else AUpdate = true; 506 | if (lastcall == "temp_humi") display_temp_and_humidity(); 507 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 508 | if (lastcall == "pressure") display_pressure(); 509 | if (lastcall == "dial") display_dial(); 510 | } 511 | 512 | void max_temp_up () { // Sets Google Charts maximum temperature 513 | max_temp += 1; 514 | if (max_temp > 60) max_temp = 60; 515 | if (lastcall == "temp_humi") display_temp_and_humidity(); 516 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 517 | if (lastcall == "pressure") display_pressure(); 518 | if (lastcall == "dial") display_dial(); 519 | } 520 | 521 | void max_temp_down () { // Sets Google Charts maximum temperature 522 | max_temp -= 1; 523 | if (max_temp <0) max_temp = 0; 524 | if (lastcall == "temp_humi") display_temp_and_humidity(); 525 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 526 | if (lastcall == "pressure") display_pressure(); 527 | if (lastcall == "dial") display_dial(); 528 | } 529 | 530 | void min_temp_up () { // Sets Google Charts minimum temperature 531 | min_temp += 1; 532 | if (lastcall == "temp_humi") display_temp_and_humidity(); 533 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 534 | if (lastcall == "pressure") display_pressure(); 535 | if (lastcall == "dial") display_dial(); 536 | } 537 | 538 | void min_temp_down () { // Sets Google Charts minimum temperature 539 | min_temp -= 1; 540 | if (min_temp < -60) min_temp = -60; 541 | if (lastcall == "temp_humi") display_temp_and_humidity(); 542 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 543 | if (lastcall == "pressure") display_pressure(); 544 | if (lastcall == "dial") display_dial(); 545 | } 546 | 547 | void LogT_D () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 548 | log_interval -= log_time_unit; 549 | if (log_interval < log_time_unit) log_interval = log_time_unit; 550 | update_log_time(); 551 | if (lastcall == "temp_humi") display_temp_and_humidity(); 552 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 553 | if (lastcall == "pressure") display_pressure(); 554 | if (lastcall == "dial") display_dial(); 555 | } 556 | 557 | void LogT_U () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 558 | log_interval += log_time_unit; 559 | update_log_time(); 560 | if (lastcall == "temp_humi") display_temp_and_humidity(); 561 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 562 | if (lastcall == "pressure") display_pressure(); 563 | if (lastcall == "dial") display_dial(); 564 | } 565 | 566 | void update_log_time() { 567 | float log_hrs; 568 | log_hrs = table_size*log_interval/time_reference; 569 | log_hrs = log_hrs / 60; // This stage should not be needed, but compiler can't calculate the result in-line! 570 | float log_mins = (log_hrs - int(log_hrs))*60; 571 | 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"; 572 | log_time += ", Free-mem:("+String(system_get_free_heap_size())+")"; 573 | } 574 | 575 | void systemSetup() { 576 | webpage = ""; // don't delete this command, it ensures the server works reliably! 577 | append_page_header(); 578 | String IPaddress = WiFi.localIP().toString(); 579 | webpage += "

System Setup, if required enter values then choose Graph or Gauge

"; 580 | webpage += ""; 581 | webpage += "
"; 582 | webpage += "Maximum Temperature on Graph axis (currently = "+String(max_temp)+char(176)+"C
"; 583 | webpage += "
"; 584 | webpage += "Minimum Temperature on Graph axis (currently = "+String(min_temp)+char(176)+"C
"; 585 | webpage += "
"; 586 | webpage += "Logging Interval (currently = "+String(log_interval)+"-Secs)
"; 587 | webpage += "
"; 588 | webpage += "Auto-scale Graph (currently = "+String(AScale?"ON":"OFF")+"
"; 589 | webpage += "
"; 590 | webpage += "Auto-update Graph (currently = "+String(AUpdate?"ON":"OFF")+"
"; 591 | webpage += "
"; 592 | webpage += "

"; 593 | webpage += "
"; 594 | append_page_footer(); 595 | server.send(200, "text/html", webpage); // Send a response to the client asking for input 596 | if (server.args() > 0 ) { // Arguments were received 597 | for ( uint8_t i = 0; i < server.args(); i++ ) { 598 | String Argument_Name = server.argName(i); 599 | String client_response = server.arg(i); 600 | if (Argument_Name == "max_temp_in") { 601 | if (client_response.toInt()) max_temp = client_response.toInt(); else max_temp = 30; 602 | } 603 | if (Argument_Name == "min_temp_in") { 604 | if (client_response.toInt() == 0) min_temp = 0; else min_temp = client_response.toInt(); 605 | } 606 | if (Argument_Name == "log_interval_in") { 607 | if (client_response.toInt()) log_interval = client_response.toInt(); else log_interval = 300; 608 | log_interval = client_response.toInt()*log_time_unit; 609 | } 610 | if (Argument_Name == "auto_scale") { 611 | if (client_response == "ON") AScale = true; else AScale = false; 612 | } 613 | if (Argument_Name == "auto_update") { 614 | if (client_response == "ON") AUpdate = true; else AUpdate = false; 615 | } 616 | } 617 | } 618 | webpage = ""; 619 | update_log_time(); 620 | } 621 | 622 | void append_page_header() { 623 | webpage = ""; 624 | if (AUpdate) webpage += ""; // 30-sec refresh time, test needed to prevent auto updates repeating some commands 625 | webpage += "BME280 Sensor Readings

Autonomous Graphing Data Logger " + version + "

"; 631 | } 632 | 633 | void append_page_footer(){ // Saves repeating many lines of code for HTML page footers 634 | webpage += ""; 643 | webpage += ""; 663 | webpage += "

©"+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))); 664 | webpage += String(char((0x84>>1)))+String(char(byte(0xd2>>1)))+String(char(0xe4>>1))+String(char(0xc8>>1))+String(char(byte(0x40>>1))); 665 | webpage += String(char(byte(0x64/2)))+String(char(byte(0x60>>1)))+String(char(byte(0x62>>1)))+String(char(0x6c>>1))+"

"; 666 | webpage += ""; 667 | } 668 | 669 | String calcDateTime(int epoch){ 670 | int seconds, minutes, hours, dayOfWeek, current_day, current_month, current_year; 671 | seconds = epoch; 672 | minutes = seconds / 60; // calculate minutes 673 | seconds -= minutes * 60; // calculate seconds 674 | hours = minutes / 60; // calculate hours 675 | minutes -= hours * 60; 676 | current_day = hours / 24; // calculate days 677 | hours -= current_day * 24; 678 | current_year = 1970; // Unix time starts in 1970 679 | dayOfWeek = 4; // on a Thursday 680 | while(1){ 681 | bool leapYear = (current_year % 4 == 0 && (current_year % 100 != 0 || current_year % 400 == 0)); 682 | uint16_t daysInYear = leapYear ? 366 : 365; 683 | if (current_day >= daysInYear) { 684 | dayOfWeek += leapYear ? 2 : 1; 685 | current_day -= daysInYear; 686 | if (dayOfWeek >= 7) dayOfWeek -= 7; 687 | ++current_year; 688 | } 689 | else 690 | { 691 | dayOfWeek += current_day; 692 | dayOfWeek %= 7; 693 | /* calculate the month and day */ 694 | static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 695 | for(current_month = 0; current_month < 12; ++current_month) { 696 | uint8_t dim = daysInMonth[current_month]; 697 | if (current_month == 1 && leapYear) ++dim; // add a day to February if a leap year 698 | if (current_day >= dim) current_day -= dim; 699 | else break; 700 | } 701 | break; 702 | } 703 | } 704 | current_month += 1; // Months are 0..11 and returned format is dd/mm/ccyy hh:mm:ss 705 | current_day += 1; 706 | String date_time = String(current_day) + "/" + String(current_month) + "/" + String(current_year) + " "; 707 | date_time += ((hours < 10) ? "0" + String(hours): String(hours)) + ":"; 708 | date_time += ((minutes < 10) ? "0" + String(minutes): String(minutes)) + ":"; 709 | date_time += ((seconds < 10) ? "0" + String(seconds): String(seconds)); 710 | return date_time; 711 | } 712 | 713 | void help() { 714 | webpage = ""; // don't delete this command, it ensures the server works reliably! 715 | append_page_header(); 716 | webpage += "
"; 717 | webpage += "Temperature & Humidity graph of readings
"; 718 | webpage += "Temperature & Dewpoint graph readings
"; 719 | webpage += "Pressure - graph of air pressure readings
"; 720 | webpage += "Gauge - displays current temperature, humidity and air pressure values
"; 721 | webpage += "Max°C⇑ - increase maximum y-axis by 1°C
"; 722 | webpage += "Max°C⇓ - decrease maximum y-axis by 1°C
"; 723 | webpage += "Min°C⇑ - increase minimum y-axis by 1°C
"; 724 | webpage += "Min°C⇓ - decrease minimum y-axis by 1°C
"; 725 | webpage += "Logging⇓ - reduce logging speed with more time between log entries
"; 726 | webpage += "Logging⇑ - increase logging speed with less time between log entries
"; 727 | webpage += "Auto-scale(ON/OFF) - toggle the graph AScale ON/OFF
"; 728 | webpage += "Auto-update(ON/OFF) - toggle screen auto-refresh ON/OFF
"; 729 | webpage += "Setup - allows some settings to be adjusted

"; 730 | webpage += "The following functions are enabled when an SD-Card reader is fitted:
"; 731 | webpage += "Log Size - display log file size in bytes
"; 732 | webpage += "View Log - stream log file contents to the screen, copy and paste into a spreadsheet using paste special, text
"; 733 | webpage += "Erase Log - erase log file, needs two approvals using this function. Any data display function resets the initial erase approval

"; 734 | webpage += "
"; 735 | append_page_footer(); 736 | server.send(200, "text/html", webpage); 737 | webpage = ""; 738 | } 739 | 740 | /* 741 | server.send(200, "text/xml", ""); 742 | WiFiClient client = server.client(); 743 | client.write(&XSD_SW_1[0], sizeof(XSD_SW_1)-1); 744 | */ 745 | 746 | -------------------------------------------------------------------------------- /ESP8266_DHT11_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino: -------------------------------------------------------------------------------- 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 16 | #include 17 | #include 18 | #include // https://github.com/tzapu/WiFiManager 19 | #include 20 | #include // Needs to be version 1.0.9 to work with ESP8266 21 | #include 22 | #include 23 | #include 24 | #include 25 | #define DHTPIN D4 // Pin which is connected to the DHT sensor. 26 | // Uncomment the type of sensor in use: 27 | #define DHTTYPE DHT11 // DHT 11 28 | //#define DHTTYPE DHT22 // DHT 22 (AM2302) 29 | //#define DHTTYPE DHT21 // DHT 21 (AM2301) 30 | DHT_Unified dht(DHTPIN, DHTTYPE,16); // Use 16 for the ESP8266 31 | 32 | String version = "v1.0"; // Version of this program 33 | WiFiClient client; 34 | 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 35 | // 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 36 | // connection request to http://your_network_ip_address:8266 to port 8266 and view your ESP server from anywhere. 37 | // 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 38 | 39 | int log_time_unit = 15; // default is 1-minute between readings, 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 40 | int time_reference= 60; // Time reference for calculating /log-time (nearly in secs) to convert to minutes 41 | int const table_size = 288; // 300 is about the maximum for the available memory, so use 12 samples/hour * 24 * 1-day = 288 42 | int index_ptr, timer_cnt, log_interval, log_count, max_temp, min_temp; 43 | String webpage,time_now,log_time,lastcall; 44 | bool SD_present, batch_not_written, AScale, auto_smooth, AUpdate, log_delete_approved; 45 | float dht_temp,dht_humi; 46 | 47 | typedef struct { 48 | int lcnt; // Sequential log count 49 | String ltime; // Time reading taken 50 | sint16_t temp; // Temperature values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 51 | sint16_t humi; // Humidity values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 52 | } record_type; 53 | 54 | record_type sensor_data[table_size+1]; // Define the data array 55 | 56 | void setup() { 57 | Serial.begin(115200); 58 | //WiFiManager intialisation. Once completed there is no need to repeat the process on the current board 59 | WiFiManager wifiManager; 60 | // 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 61 | // so restart the ESP8266 and connect your PC to the wireless access point called 'ESP8266_AP' or whatever you call it below in "" 62 | // wifiManager.resetSettings(); // Command to be included if needed, then connect to http://192.168.4.1/ and follow instructions to make the WiFi connection 63 | // Set a timeout until configuration is turned off, useful to retry or go to sleep in n-seconds 64 | wifiManager.setTimeout(180); 65 | //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 66 | if(!wifiManager.autoConnect("ESP8266_AP")) { 67 | Serial.println(F("failed to connect and timeout occurred")); 68 | delay(3000); 69 | ESP.reset(); //reset and try again 70 | delay(5000); 71 | } 72 | // At this stage the WiFi manager will have successfully connected to a network, or if not will try again in 180-seconds 73 | //---------------------------------------------------------------------- 74 | Serial.println(F("WiFi connected..")); 75 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); 76 | server.begin(); Serial.println(F("Webserver started...")); // Start the webserver 77 | Serial.println("Use this URL to connect: http://"+WiFi.localIP().toString()+"/");// Print the IP address 78 | //---------------------------------------------------------------------- 79 | Serial.print(F("Initializing SD card...")); 80 | if (!SD.begin(D8)) { // see if the card is present and can be initialised. Wemos SD-Card CS uses D8 81 | Serial.println(F("Card failed, or not present")); 82 | SD_present = false; 83 | } else SD_present = true; 84 | if (SD_present) Serial.println(F("Card initialised.")); else Serial.println(F("Card not present, no SD Card data logging possible")); 85 | dht.begin(); 86 | //---------------------------------------------------------------------- 87 | server.on("/", systemSetup); // The client connected with no arguments e.g. http:192.160.0.40/ 88 | server.on("/TempHumi", display_temp_and_humidity); 89 | server.on("/TempDewp", display_temp_and_dewpoint); 90 | server.on("/Dialview", display_dial); 91 | server.on("/AScale", auto_scale); 92 | server.on("/AUpdate", auto_update); 93 | server.on("/Setup", systemSetup); 94 | server.on("/Help", help); 95 | server.on("/MaxT_U", max_temp_up); 96 | server.on("/MaxT_D", max_temp_down); 97 | server.on("/MinT_U", min_temp_up); 98 | server.on("/MinT_D", min_temp_down); 99 | server.on("/LogT_U", logtime_up); 100 | server.on("/LogT_D", logtime_down); 101 | if (SD_present) { 102 | server.on("/SDview", SD_view); 103 | server.on("/SDerase", SD_erase); 104 | server.on("/SDstats", SD_stats); 105 | } 106 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // Start time server 107 | index_ptr = 0; // The array pointer that varies from 0 to table_size 108 | log_count = 0; // Keeps a count of readings taken 109 | AScale = false; // Google charts can AScale axis, this switches the function on/off 110 | max_temp = 30; // Maximum displayed temperature as default 111 | min_temp = -10; // Minimum displayed temperature as default 112 | 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 113 | 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. 114 | lastcall = "temp_humi"; // To determine what requested the AScale change 115 | 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 116 | timer_cnt = log_interval + 1; // To trigger first table update, essential 117 | update_log_time(); // Update the log_time 118 | log_delete_approved = false; // Used to prevent accidental deletion of card contents, requires two approvals 119 | reset_array(); // Clear storage array before use 120 | prefill_array(); // Load old data from SD-Card back into display and readings array 121 | //Serial.println(system_get_free_heap_size()); // diagnostic print to check for available RAM 122 | time_t now = time(nullptr); 123 | delay(2000); // Wait for time to start 124 | //Serial.println(time(&now)); // Unix time epoch 125 | Serial.print(F("Logging started at: ")); Serial.println(calcDateTime(time(&now))); 126 | /*you can also obtain time and date like this 127 | struct tm *now_tm; 128 | int hour,min,second,day,month,year; 129 | now = time(NULL); 130 | now_tm = localtime(&now); 131 | hour = now_tm->tm_hour; 132 | min = now_tm->tm_min; 133 | second = now_tm->tm_sec; 134 | day = now_tm->tm_mday; 135 | month = now_tm->tm_mon; 136 | year = now_tm->tm_year + 1900; 137 | Serial.print(hour);Serial.print(":");Serial.print(min);Serial.print(":");Serial.println(second); 138 | Serial.print(day);Serial.print("/");Serial.print(month);Serial.print("/");Serial.println(year); 139 | */ 140 | } 141 | 142 | void loop() { 143 | server.handleClient(); 144 | sensor_t sensor; 145 | sensors_event_t event; 146 | dht.temperature().getEvent(&event); 147 | if (isnan(event.temperature)) Serial.println("Error reading temperature!"); else dht_temp = event.temperature*10; 148 | dht.humidity().getEvent(&event); 149 | if (isnan(event.relative_humidity)) Serial.println("Error reading humidity!"); else dht_humi = event.relative_humidity*10; 150 | 151 | time_t now = time(nullptr); 152 | time_now = String(ctime(&now)).substring(0,24); // Remove unwanted characters 153 | 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. 154 | timer_cnt = 0; // log_interval values are 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 155 | log_count += 1; // Increase logging event count 156 | sensor_data[index_ptr].lcnt = log_count; // Record current log number, time, temp and humidity readings 157 | sensor_data[index_ptr].temp = dht_temp; 158 | sensor_data[index_ptr].humi = dht_humi; 159 | sensor_data[index_ptr].ltime = calcDateTime(time(&now)); // time stamp of reading 'dd/mm/yy hh:mm:ss' 160 | if (SD_present){ // If the SD-Card is present and board fitted then append the next reading to the log file called 'datalog.txt' 161 | File dataFile = SD.open("datalog.txt", FILE_WRITE); 162 | if (dataFile) { // if the file is available, write to it 163 | 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 164 | } 165 | dataFile.close(); 166 | } 167 | index_ptr += 1; // Increment data record pointer 168 | 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 169 | index_ptr = table_size; 170 | 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 171 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 172 | sensor_data[i].temp = sensor_data[i+1].temp; 173 | sensor_data[i].humi = sensor_data[i+1].humi; 174 | sensor_data[i].ltime = sensor_data[i+1].ltime; 175 | } 176 | sensor_data[table_size].lcnt = log_count; 177 | sensor_data[table_size].temp = dht_temp; 178 | sensor_data[table_size].humi = dht_humi; 179 | sensor_data[table_size].ltime = calcDateTime(time(&now)); 180 | } 181 | } 182 | timer_cnt += 1; // Readings set by value of log_interval each 40 = 1min 183 | delay(498); // Delay before next check for a client, adjust for 1-sec repeat interval. Temperature readings take some time to complete. 184 | //Serial.println(millis()); 185 | } 186 | 187 | void prefill_array(){ // After power-down or restart and if the SD-Card has readings, load them back in 188 | if (SD_present){ 189 | File dataFile = SD.open("datalog.txt", FILE_READ); 190 | while (dataFile.available()) { // if the file is available, read from it 191 | int read_ahead = dataFile.parseInt(); // Sometimes at the end of file, NULL data is returned, this tests for that 192 | 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! 193 | sensor_data[index_ptr].lcnt = read_ahead ; 194 | sensor_data[index_ptr].temp = dataFile.parseFloat()*10; 195 | sensor_data[index_ptr].humi = dataFile.parseFloat()*10; 196 | sensor_data[index_ptr].ltime = dataFile.readStringUntil('\n'); 197 | index_ptr += 1; 198 | log_count += 1; 199 | } 200 | if (index_ptr > table_size) { 201 | for (int i = 0; i < table_size; i++) { 202 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 203 | sensor_data[i].temp = sensor_data[i+1].temp; 204 | sensor_data[i].humi = sensor_data[i+1].humi; 205 | sensor_data[i].ltime = sensor_data[i+1].ltime; 206 | } 207 | index_ptr = table_size; 208 | } 209 | } 210 | dataFile.close(); 211 | 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 212 | // 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. 213 | float last_temp,last_humi; 214 | for (int i = 1; i < table_size; i++) { 215 | last_temp = sensor_data[i].temp; 216 | last_humi = sensor_data[i].humi; 217 | // Correct next reading if it is more than 10% different from last values 218 | 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 219 | 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; 220 | } 221 | } 222 | } 223 | } 224 | 225 | void display_temp_and_humidity() { // Processes a clients request for a graph of the data 226 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 227 | // 228 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 236 | webpage += F(""; 266 | //webpage += ""; 267 | webpage += "
"; 268 | //----------------------------------- 269 | append_page_footer(); 270 | server.send(200, "text/html", webpage); 271 | webpage = ""; 272 | lastcall = "temp_humi"; 273 | } 274 | 275 | void display_temp_and_dewpoint() { // Processes a clients request for a graph of the data 276 | float dew_point; 277 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 278 | // 279 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 287 | webpage += F(""; 314 | //webpage += ""; 315 | webpage += "
"; 316 | //----------------------------------- 317 | append_page_footer(); 318 | server.send(200, "text/html", webpage); 319 | webpage = ""; 320 | lastcall = "temp_dewp"; 321 | } 322 | 323 | void display_dial (){ // Processes a clients request for a dial-view of the data 324 | log_delete_approved = false; // PRevent accidental SD-Card deletion 325 | webpage = ""; // don't delete this command, it ensures the server works reliably! 326 | append_page_header(); 327 | //############### New API call needed 328 | webpage += ""; 329 | webpage += ""; 350 | webpage += ""; 351 | webpage += "
"; 352 | webpage += "
"; 353 | webpage += "
"; 354 | webpage += "
"; 355 | webpage += "
"; 356 | append_page_footer(); 357 | server.send(200, "text/html", webpage); 358 | webpage = ""; 359 | lastcall = "dial"; 360 | } 361 | 362 | float Calc_DewPoint(float temp, float humi) { 363 | return 243.04*(log(humi/100)+((17.625*temp)/(243.04+temp)))/(17.625-log(humi/100)-((17.625*temp)/(243.04+temp))); 364 | } 365 | 366 | void reset_array() { 367 | for (int i = 0; i <= table_size; i++) { 368 | sensor_data[i].lcnt = 0; 369 | sensor_data[i].temp = 0; 370 | sensor_data[i].humi = 0; 371 | sensor_data[i].ltime = ""; 372 | } 373 | } 374 | 375 | // 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 376 | void SD_view() { 377 | if (SD_present) { 378 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 379 | if (dataFile) { 380 | if (dataFile.available()) { // If data is available and present 381 | String dataType = "application/octet-stream"; 382 | if (server.streamFile(dataFile, dataType) != dataFile.size()) {Serial.print(F("Sent less data than expected!")); } 383 | } 384 | } 385 | dataFile.close(); // close the file: 386 | } 387 | webpage = ""; 388 | } 389 | 390 | void SD_erase() { // Erase the datalog file 391 | webpage = ""; // don't delete this command, it ensures the server works reliably! 392 | append_page_header(); 393 | if (AUpdate) webpage += ""; // 30-sec refresh time and test is needed to stop auto updates repeating some commands 394 | if (log_delete_approved) { 395 | if (SD_present) { 396 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 397 | if (dataFile) if (SD.remove("datalog.txt")) Serial.println(F("File deleted successfully")); 398 | webpage += "

Log file 'datalog.txt' has been erased

"; 399 | log_count = 0; 400 | index_ptr = 0; 401 | timer_cnt = 2000; // To trigger first table update, essential 402 | log_delete_approved = false; // Re-enable sd card deletion 403 | } 404 | } 405 | else { 406 | log_delete_approved = true; 407 | webpage += "

Log file erasing is enabled, repeat this option to erase the log. Graph or Dial Views disable erasing again

"; 408 | } 409 | append_page_footer(); 410 | server.send(200, "text/html", webpage); 411 | webpage = ""; 412 | } 413 | 414 | void SD_stats(){ // Display file size of the datalog file 415 | webpage = ""; // don't delete this command, it ensures the server works reliably! 416 | append_page_header(); 417 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 418 | webpage += "

Data Log file size = "+String(dataFile.size())+"-Bytes

"; 419 | dataFile.close(); 420 | append_page_footer(); 421 | server.send(200, "text/html", webpage); 422 | webpage = ""; 423 | } 424 | 425 | void auto_scale () { // Google Charts can auto-scale graph axis, this turns it on/off 426 | if (AScale) AScale = false; else AScale = true; 427 | if (lastcall == "temp_humi") display_temp_and_humidity(); 428 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 429 | if (lastcall == "dial") display_dial(); 430 | } 431 | 432 | void auto_update () { // Google Charts can auto-scale graph axis, this turns it on/off 433 | if (AUpdate) AUpdate = false; else AUpdate = true; 434 | if (lastcall == "temp_humi") display_temp_and_humidity(); 435 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 436 | if (lastcall == "dial") display_dial(); 437 | } 438 | 439 | void max_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off 440 | max_temp += 1; 441 | if (max_temp >60) max_temp = 60; 442 | if (lastcall == "temp_humi") display_temp_and_humidity(); 443 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 444 | if (lastcall == "dial") display_dial(); 445 | } 446 | 447 | void max_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off 448 | max_temp -= 1; 449 | if (max_temp <0) max_temp = 0; 450 | if (lastcall == "temp_humi") display_temp_and_humidity(); 451 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 452 | if (lastcall == "dial") display_dial(); 453 | } 454 | 455 | void min_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off 456 | min_temp += 1; 457 | if (lastcall == "temp_humi") display_temp_and_humidity(); 458 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 459 | if (lastcall == "dial") display_dial(); 460 | } 461 | 462 | void min_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off 463 | min_temp -= 1; 464 | if (min_temp < -60) min_temp = -60; 465 | if (lastcall == "temp_humi") display_temp_and_humidity(); 466 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 467 | if (lastcall == "dial") display_dial(); 468 | } 469 | 470 | void logtime_down () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 471 | log_interval -= log_time_unit; 472 | if (log_interval < log_time_unit) log_interval = log_time_unit; 473 | update_log_time(); 474 | if (lastcall == "temp_humi") display_temp_and_humidity(); 475 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 476 | if (lastcall == "dial") display_dial(); 477 | } 478 | 479 | void logtime_up () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 480 | log_interval += log_time_unit; 481 | update_log_time(); 482 | if (lastcall == "temp_humi") display_temp_and_humidity(); 483 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 484 | if (lastcall == "dial") display_dial(); 485 | } 486 | 487 | void update_log_time() { 488 | float log_hrs; 489 | log_hrs = table_size*log_interval/time_reference; 490 | log_hrs = log_hrs / 60; // Should not be needed, but compiler cant' calcuate the result in-line! 491 | float log_mins = (log_hrs - int(log_hrs))*60; 492 | 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"; 493 | log_time += ", Free-mem:("+String(system_get_free_heap_size())+")"; 494 | } 495 | 496 | void systemSetup() { 497 | webpage = ""; // don't delete this command, it ensures the server works reliably! 498 | append_page_header(); 499 | String IPaddress = WiFi.localIP().toString(); 500 | webpage += "

System Setup, if required enter values then choose Graph or Dial

"; 501 | webpage += ""; 502 | webpage += "
"; 503 | webpage += "Maximum Temperature on Graph axis (currently = "+String(max_temp)+char(176)+"C
"; 504 | webpage += "
"; 505 | webpage += "Minimum Temperature on Graph axis (currently = "+String(min_temp)+char(176)+"C
"; 506 | webpage += "
"; 507 | webpage += "Logging Interval (currently = "+String(log_interval)+"-Secs)
"; 508 | webpage += "
"; 509 | webpage += "Auto-scale Graph (currently = "+String(AScale?"ON":"OFF")+"
"; 510 | webpage += "
"; 511 | webpage += "Auto-update Graph (currently = "+String(AUpdate?"ON":"OFF")+"
"; 512 | webpage += "
"; 513 | webpage += "

"; 514 | webpage += "
"; 515 | append_page_footer(); 516 | server.send(200, "text/html", webpage); // Send a response to the client asking for input 517 | if (server.args() > 0 ) { // Arguments were received 518 | for ( uint8_t i = 0; i < server.args(); i++ ) { 519 | String Argument_Name = server.argName(i); 520 | String client_response = server.arg(i); 521 | if (Argument_Name == "max_temp_in") { 522 | if (client_response.toInt()) max_temp = client_response.toInt(); else max_temp = 30; 523 | } 524 | if (Argument_Name == "min_temp_in") { 525 | if (client_response.toInt() == 0) min_temp = 0; else min_temp = client_response.toInt(); 526 | } 527 | if (Argument_Name == "log_interval_in") { 528 | if (client_response.toInt()) log_interval = client_response.toInt(); else log_interval = 300; 529 | log_interval = client_response.toInt()*log_time_unit; 530 | } 531 | if (Argument_Name == "auto_scale") { 532 | if (client_response == "ON") AScale = true; else AScale = false; 533 | } 534 | if (Argument_Name == "auto_update") { 535 | if (client_response == "ON") AUpdate = true; else AUpdate = false; 536 | } 537 | } 538 | } 539 | webpage = ""; 540 | update_log_time(); 541 | } 542 | 543 | void append_page_header() { 544 | webpage = ""; 545 | if (AUpdate) webpage += ""; // 30-sec refresh time, test needed to prevent auto updates repeating some commands 546 | webpage += "DHT11 Sensor Readings

Autonomous Graphing Data Logger " + version + "

"; 552 | } 553 | 554 | void append_page_footer(){ // Saves repeating many lines of code for HTML page footers 555 | webpage += ""; 564 | webpage += ""; 583 | webpage += "

©"+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))); 584 | webpage += String(char((0x84>>1)))+String(char(byte(0xd2>>1)))+String(char(0xe4>>1))+String(char(0xc8>>1))+String(char(byte(0x40>>1))); 585 | webpage += String(char(byte(0x64/2)))+String(char(byte(0x60>>1)))+String(char(byte(0x62>>1)))+String(char(0x6c>>1))+"

"; 586 | webpage += ""; 587 | } 588 | 589 | String calcDateTime(int epoch){ 590 | int seconds, minutes, hours, dayOfWeek, current_day, current_month, current_year; 591 | seconds = epoch; 592 | minutes = seconds / 60; // calculate minutes 593 | seconds -= minutes * 60; // calculate seconds 594 | hours = minutes / 60; // calculate hours 595 | minutes -= hours * 60; 596 | current_day = hours / 24; // calculate days 597 | hours -= current_day * 24; 598 | current_year = 1970; // Unix time starts in 1970 599 | dayOfWeek = 4; // on a Thursday 600 | while(1){ 601 | bool leapYear = (current_year % 4 == 0 && (current_year % 100 != 0 || current_year % 400 == 0)); 602 | uint16_t daysInYear = leapYear ? 366 : 365; 603 | if (current_day >= daysInYear) { 604 | dayOfWeek += leapYear ? 2 : 1; 605 | current_day -= daysInYear; 606 | if (dayOfWeek >= 7) dayOfWeek -= 7; 607 | ++current_year; 608 | } 609 | else 610 | { 611 | dayOfWeek += current_day; 612 | dayOfWeek %= 7; 613 | /* calculate the month and day */ 614 | static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 615 | for(current_month = 0; current_month < 12; ++current_month) { 616 | uint8_t dim = daysInMonth[current_month]; 617 | if (current_month == 1 && leapYear) ++dim; // add a day to February if a leap year 618 | if (current_day >= dim) current_day -= dim; 619 | else break; 620 | } 621 | break; 622 | } 623 | } 624 | current_month += 1; // Months are 0..11 and returned format is dd/mm/ccyy hh:mm:ss 625 | current_day += 1; 626 | String date_time = String(current_day) + "/" + String(current_month) + "/" + String(current_year) + " "; 627 | date_time += ((hours < 10) ? "0" + String(hours): String(hours)) + ":"; 628 | date_time += ((minutes < 10) ? "0" + String(minutes): String(minutes)) + ":"; 629 | date_time += ((seconds < 10) ? "0" + String(seconds): String(seconds)); 630 | return date_time; 631 | } 632 | 633 | void help() { 634 | webpage = ""; // don't delete this command, it ensures the server works reliably! 635 | append_page_header(); 636 | webpage += "
"; 637 | webpage += "Temperature&Humidity - a graph of temperature and humidity
"; 638 | webpage += "Temperature&Dewpoint - a graph of temperature and dewpoint
"; 639 | webpage += "Dial - displays current temperature and humidity values
"; 640 | webpage += "Max°C⇑ - increase maximum y-axis by 1°C;
"; 641 | webpage += "Max°C⇓ - decrease maximum y-axis by 1°C;
"; 642 | webpage += "Min°C⇑ - increase minimum y-axis by 1°C;
"; 643 | webpage += "Min°C⇓ - decrease minimum y-axis by 1°C;
"; 644 | webpage += "Logging⇓ - reduce logging speed with more time between log entries
"; 645 | webpage += "Logging⇑ - increase logging speed with less time between log entries
"; 646 | webpage += "Auto-scale(ON/OFF) - toggle the graph Auto-scale ON/OFF
"; 647 | webpage += "Auto-update(ON/OFF) - toggle screen Auto-refresh ON/OFF
"; 648 | webpage += "Setup - allows some settings to be adjusted

"; 649 | webpage += "The following functions are enabled when an SD-Card reader is fitted:
"; 650 | webpage += "Log Size - display log file size in bytes
"; 651 | webpage += "View Log - stream log file contents to the screen, copy and paste into a spreadsheet using paste special, text
"; 652 | webpage += "Erase Log - erase log file, needs two approvals using this function. Any data display function resets the initial erase approval

"; 653 | webpage += "
"; 654 | append_page_footer(); 655 | server.send(200, "text/html", webpage); 656 | webpage = ""; 657 | } 658 | 659 | -------------------------------------------------------------------------------- /ESP8266_DHT22_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino: -------------------------------------------------------------------------------- 1 | /* ESP8266 plus WEMOS DHT22 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 16 | #include 17 | #include 18 | #include // https://github.com/tzapu/WiFiManager 19 | #include 20 | #include // Needs to be version 1.0.9 to work with ESP8266 21 | #include 22 | #include 23 | #include 24 | #include 25 | #define DHTPIN D4 // Pin which is connected to the DHT sensor. 26 | // Uncomment the type of sensor in use: 27 | //#define DHTTYPE DHT11 // DHT 11 28 | #define DHTTYPE DHT22 // DHT 22 (AM2302) 29 | //#define DHTTYPE DHT21 // DHT 21 (AM2301) 30 | DHT_Unified dht(DHTPIN, DHTTYPE,16); // Use 16 for the ESP8266 31 | 32 | String version = "v1.0"; // Version of this program 33 | WiFiClient client; 34 | 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 35 | // 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 36 | // connection request to http://your_network_ip_address:8266 to port 8266 and view your ESP server from anywhere. 37 | // 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 38 | 39 | int log_time_unit = 15; // default is 1-minute between readings, 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 40 | int time_reference= 60; // Time reference for calculating /log-time (nearly in secs) to convert to minutes 41 | int const table_size = 288; // 300 is about the maximum for the available memory, so use 12 samples/hour * 24 * 1-day = 288 42 | int index_ptr, timer_cnt, log_interval, log_count, max_temp, min_temp; 43 | String webpage,time_now,log_time,lastcall; 44 | bool SD_present, batch_not_written, AScale, auto_smooth, AUpdate, log_delete_approved; 45 | float dht_temp,dht_humi; 46 | 47 | typedef struct { 48 | int lcnt; // Sequential log count 49 | String ltime; // Time reading taken 50 | sint16_t temp; // Temperature values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 51 | sint16_t humi; // Humidity values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 52 | } record_type; 53 | 54 | record_type sensor_data[table_size+1]; // Define the data array 55 | 56 | void setup() { 57 | Serial.begin(115200); 58 | //WiFiManager intialisation. Once completed there is no need to repeat the process on the current board 59 | WiFiManager wifiManager; 60 | // 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 61 | // so restart the ESP8266 and connect your PC to the wireless access point called 'ESP8266_AP' or whatever you call it below in "" 62 | // wifiManager.resetSettings(); // Command to be included if needed, then connect to http://192.168.4.1/ and follow instructions to make the WiFi connection 63 | // Set a timeout until configuration is turned off, useful to retry or go to sleep in n-seconds 64 | wifiManager.setTimeout(180); 65 | //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 66 | if(!wifiManager.autoConnect("ESP8266_AP")) { 67 | Serial.println(F("failed to connect and timeout occurred")); 68 | delay(3000); 69 | ESP.reset(); //reset and try again 70 | delay(5000); 71 | } 72 | // At this stage the WiFi manager will have successfully connected to a network, or if not will try again in 180-seconds 73 | //---------------------------------------------------------------------- 74 | Serial.println(F("WiFi connected..")); 75 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); 76 | server.begin(); Serial.println(F("Webserver started...")); // Start the webserver 77 | Serial.println("Use this URL to connect: http://"+WiFi.localIP().toString()+"/");// Print the IP address 78 | //---------------------------------------------------------------------- 79 | Serial.print(F("Initializing SD card...")); 80 | if (!SD.begin(D8)) { // see if the card is present and can be initialised. Wemos SD-Card CS uses D8 81 | Serial.println(F("Card failed, or not present")); 82 | SD_present = false; 83 | } else SD_present = true; 84 | if (SD_present) Serial.println(F("Card initialised.")); else Serial.println(F("Card not present, no SD Card data logging possible")); 85 | dht.begin(); 86 | //---------------------------------------------------------------------- 87 | server.on("/", systemSetup); // The client connected with no arguments e.g. http:192.160.0.40/ 88 | server.on("/TempHumi", display_temp_and_humidity); 89 | server.on("/TempDewp", display_temp_and_dewpoint); 90 | server.on("/Dialview", display_dial); 91 | server.on("/AScale", auto_scale); 92 | server.on("/AUpdate", auto_update); 93 | server.on("/Setup", systemSetup); 94 | server.on("/Help", help); 95 | server.on("/MaxT_U", max_temp_up); 96 | server.on("/MaxT_D", max_temp_down); 97 | server.on("/MinT_U", min_temp_up); 98 | server.on("/MinT_D", min_temp_down); 99 | server.on("/LogT_U", logtime_up); 100 | server.on("/LogT_D", logtime_down); 101 | if (SD_present) { 102 | server.on("/SDview", SD_view); 103 | server.on("/SDerase", SD_erase); 104 | server.on("/SDstats", SD_stats); 105 | } 106 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // Start time server 107 | index_ptr = 0; // The array pointer that varies from 0 to table_size 108 | log_count = 0; // Keeps a count of readings taken 109 | AScale = false; // Google charts can AScale axis, this switches the function on/off 110 | max_temp = 30; // Maximum displayed temperature as default 111 | min_temp = -10; // Minimum displayed temperature as default 112 | 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 113 | 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. 114 | lastcall = "temp_humi"; // To determine what requested the AScale change 115 | 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 116 | timer_cnt = log_interval + 1; // To trigger first table update, essential 117 | update_log_time(); // Update the log_time 118 | log_delete_approved = false; // Used to prevent accidental deletion of card contents, requires two approvals 119 | reset_array(); // Clear storage array before use 120 | prefill_array(); // Load old data from SD-Card back into display and readings array 121 | //Serial.println(system_get_free_heap_size()); // diagnostic print to check for available RAM 122 | time_t now = time(nullptr); 123 | delay(2000); // Wait for time to start 124 | //Serial.println(time(&now)); // Unix time epoch 125 | Serial.print(F("Logging started at: ")); Serial.println(calcDateTime(time(&now))); 126 | /*you can also obtain time and date like this 127 | struct tm *now_tm; 128 | int hour,min,second,day,month,year; 129 | now = time(NULL); 130 | now_tm = localtime(&now); 131 | hour = now_tm->tm_hour; 132 | min = now_tm->tm_min; 133 | second = now_tm->tm_sec; 134 | day = now_tm->tm_mday; 135 | month = now_tm->tm_mon; 136 | year = now_tm->tm_year + 1900; 137 | Serial.print(hour);Serial.print(":");Serial.print(min);Serial.print(":");Serial.println(second); 138 | Serial.print(day);Serial.print("/");Serial.print(month);Serial.print("/");Serial.println(year); 139 | */ 140 | } 141 | 142 | void loop() { 143 | server.handleClient(); 144 | sensor_t sensor; 145 | sensors_event_t event; 146 | dht.temperature().getEvent(&event); 147 | if (isnan(event.temperature)) Serial.println("Error reading temperature!"); else dht_temp = event.temperature*10; 148 | dht.humidity().getEvent(&event); 149 | if (isnan(event.relative_humidity)) Serial.println("Error reading humidity!"); else dht_humi = event.relative_humidity*10; 150 | 151 | time_t now = time(nullptr); 152 | time_now = String(ctime(&now)).substring(0,24); // Remove unwanted characters 153 | 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. 154 | timer_cnt = 0; // log_interval values are 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 155 | log_count += 1; // Increase logging event count 156 | sensor_data[index_ptr].lcnt = log_count; // Record current log number, time, temp and humidity readings 157 | sensor_data[index_ptr].temp = dht_temp; 158 | sensor_data[index_ptr].humi = dht_humi; 159 | sensor_data[index_ptr].ltime = calcDateTime(time(&now)); // time stamp of reading 'dd/mm/yy hh:mm:ss' 160 | if (SD_present){ // If the SD-Card is present and board fitted then append the next reading to the log file called 'datalog.txt' 161 | File dataFile = SD.open("datalog.txt", FILE_WRITE); 162 | if (dataFile) { // if the file is available, write to it 163 | 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 164 | } 165 | dataFile.close(); 166 | } 167 | index_ptr += 1; // Increment data record pointer 168 | 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 169 | index_ptr = table_size; 170 | 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 171 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 172 | sensor_data[i].temp = sensor_data[i+1].temp; 173 | sensor_data[i].humi = sensor_data[i+1].humi; 174 | sensor_data[i].ltime = sensor_data[i+1].ltime; 175 | } 176 | sensor_data[table_size].lcnt = log_count; 177 | sensor_data[table_size].temp = dht_temp; 178 | sensor_data[table_size].humi = dht_humi; 179 | sensor_data[table_size].ltime = calcDateTime(time(&now)); 180 | } 181 | } 182 | timer_cnt += 1; // Readings set by value of log_interval each 40 = 1min 183 | delay(498); // Delay before next check for a client, adjust for 1-sec repeat interval. Temperature readings take some time to complete. 184 | //Serial.println(millis()); 185 | } 186 | 187 | void prefill_array(){ // After power-down or restart and if the SD-Card has readings, load them back in 188 | if (SD_present){ 189 | File dataFile = SD.open("datalog.txt", FILE_READ); 190 | while (dataFile.available()) { // if the file is available, read from it 191 | int read_ahead = dataFile.parseInt(); // Sometimes at the end of file, NULL data is returned, this tests for that 192 | 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! 193 | sensor_data[index_ptr].lcnt = read_ahead ; 194 | sensor_data[index_ptr].temp = dataFile.parseFloat()*10; 195 | sensor_data[index_ptr].humi = dataFile.parseFloat()*10; 196 | sensor_data[index_ptr].ltime = dataFile.readStringUntil('\n'); 197 | index_ptr += 1; 198 | log_count += 1; 199 | } 200 | if (index_ptr > table_size) { 201 | for (int i = 0; i < table_size; i++) { 202 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 203 | sensor_data[i].temp = sensor_data[i+1].temp; 204 | sensor_data[i].humi = sensor_data[i+1].humi; 205 | sensor_data[i].ltime = sensor_data[i+1].ltime; 206 | } 207 | index_ptr = table_size; 208 | } 209 | } 210 | dataFile.close(); 211 | 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 212 | // 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. 213 | float last_temp,last_humi; 214 | for (int i = 1; i < table_size; i++) { 215 | last_temp = sensor_data[i].temp; 216 | last_humi = sensor_data[i].humi; 217 | // Correct next reading if it is more than 10% different from last values 218 | 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 219 | 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; 220 | } 221 | } 222 | } 223 | } 224 | 225 | void display_temp_and_humidity() { // Processes a clients request for a graph of the data 226 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 227 | // 228 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 236 | webpage += F(""; 266 | //webpage += ""; 267 | webpage += "
"; 268 | //----------------------------------- 269 | append_page_footer(); 270 | server.send(200, "text/html", webpage); 271 | webpage = ""; 272 | lastcall = "temp_humi"; 273 | } 274 | 275 | void display_temp_and_dewpoint() { // Processes a clients request for a graph of the data 276 | float dew_point; 277 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 278 | // 279 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 287 | webpage += F(""; 314 | //webpage += ""; 315 | webpage += "
"; 316 | //----------------------------------- 317 | append_page_footer(); 318 | server.send(200, "text/html", webpage); 319 | webpage = ""; 320 | lastcall = "temp_dewp"; 321 | } 322 | 323 | void display_dial (){ // Processes a clients request for a dial-view of the data 324 | log_delete_approved = false; // PRevent accidental SD-Card deletion 325 | webpage = ""; // don't delete this command, it ensures the server works reliably! 326 | append_page_header(); 327 | //############### New API call needed 328 | webpage += ""; 329 | webpage += ""; 350 | webpage += ""; 351 | webpage += "
"; 352 | webpage += "
"; 353 | webpage += "
"; 354 | webpage += "
"; 355 | append_page_footer(); 356 | server.send(200, "text/html", webpage); 357 | webpage = ""; 358 | lastcall = "dial"; 359 | } 360 | 361 | float Calc_DewPoint(float temp, float humi) { 362 | return 243.04*(log(humi/100)+((17.625*temp)/(243.04+temp)))/(17.625-log(humi/100)-((17.625*temp)/(243.04+temp))); 363 | } 364 | 365 | void reset_array() { 366 | for (int i = 0; i <= table_size; i++) { 367 | sensor_data[i].lcnt = 0; 368 | sensor_data[i].temp = 0; 369 | sensor_data[i].humi = 0; 370 | sensor_data[i].ltime = ""; 371 | } 372 | } 373 | 374 | // 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 375 | void SD_view() { 376 | if (SD_present) { 377 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 378 | if (dataFile) { 379 | if (dataFile.available()) { // If data is available and present 380 | String dataType = "application/octet-stream"; 381 | if (server.streamFile(dataFile, dataType) != dataFile.size()) {Serial.print(F("Sent less data than expected!")); } 382 | } 383 | } 384 | dataFile.close(); // close the file: 385 | } 386 | webpage = ""; 387 | } 388 | 389 | void SD_erase() { // Erase the datalog file 390 | webpage = ""; // don't delete this command, it ensures the server works reliably! 391 | append_page_header(); 392 | if (AUpdate) webpage += ""; // 30-sec refresh time and test is needed to stop auto updates repeating some commands 393 | if (log_delete_approved) { 394 | if (SD_present) { 395 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 396 | if (dataFile) if (SD.remove("datalog.txt")) Serial.println(F("File deleted successfully")); 397 | webpage += "

Log file 'datalog.txt' has been erased

"; 398 | log_count = 0; 399 | index_ptr = 0; 400 | timer_cnt = 2000; // To trigger first table update, essential 401 | log_delete_approved = false; // Re-enable sd card deletion 402 | } 403 | } 404 | else { 405 | log_delete_approved = true; 406 | webpage += "

Log file erasing is enabled, repeat this option to erase the log. Graph or Dial Views disable erasing again

"; 407 | } 408 | append_page_footer(); 409 | server.send(200, "text/html", webpage); 410 | webpage = ""; 411 | } 412 | 413 | void SD_stats(){ // Display file size of the datalog file 414 | webpage = ""; // don't delete this command, it ensures the server works reliably! 415 | append_page_header(); 416 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 417 | webpage += "

Data Log file size = "+String(dataFile.size())+"-Bytes

"; 418 | dataFile.close(); 419 | append_page_footer(); 420 | server.send(200, "text/html", webpage); 421 | webpage = ""; 422 | } 423 | 424 | void auto_scale () { // Google Charts can auto-scale graph axis, this turns it on/off 425 | if (AScale) AScale = false; else AScale = true; 426 | if (lastcall == "temp_humi") display_temp_and_humidity(); 427 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 428 | if (lastcall == "dial") display_dial(); 429 | } 430 | 431 | void auto_update () { // Google Charts can auto-scale graph axis, this turns it on/off 432 | if (AUpdate) AUpdate = false; else AUpdate = true; 433 | if (lastcall == "temp_humi") display_temp_and_humidity(); 434 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 435 | if (lastcall == "dial") display_dial(); 436 | } 437 | 438 | void max_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off 439 | max_temp += 1; 440 | if (max_temp >60) max_temp = 60; 441 | if (lastcall == "temp_humi") display_temp_and_humidity(); 442 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 443 | if (lastcall == "dial") display_dial(); 444 | } 445 | 446 | void max_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off 447 | max_temp -= 1; 448 | if (max_temp <0) max_temp = 0; 449 | if (lastcall == "temp_humi") display_temp_and_humidity(); 450 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 451 | if (lastcall == "dial") display_dial(); 452 | } 453 | 454 | void min_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off 455 | min_temp += 1; 456 | if (lastcall == "temp_humi") display_temp_and_humidity(); 457 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 458 | if (lastcall == "dial") display_dial(); 459 | } 460 | 461 | void min_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off 462 | min_temp -= 1; 463 | if (min_temp < -60) min_temp = -60; 464 | if (lastcall == "temp_humi") display_temp_and_humidity(); 465 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 466 | if (lastcall == "dial") display_dial(); 467 | } 468 | 469 | void logtime_down () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 470 | log_interval -= log_time_unit; 471 | if (log_interval < log_time_unit) log_interval = log_time_unit; 472 | update_log_time(); 473 | if (lastcall == "temp_humi") display_temp_and_humidity(); 474 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 475 | if (lastcall == "dial") display_dial(); 476 | } 477 | 478 | void logtime_up () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 479 | log_interval += log_time_unit; 480 | update_log_time(); 481 | if (lastcall == "temp_humi") display_temp_and_humidity(); 482 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 483 | if (lastcall == "dial") display_dial(); 484 | } 485 | 486 | void update_log_time() { 487 | float log_hrs; 488 | log_hrs = table_size*log_interval/time_reference; 489 | log_hrs = log_hrs / 60; // Should not be needed, but compiler cant' calcuate the result in-line! 490 | float log_mins = (log_hrs - int(log_hrs))*60; 491 | 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"; 492 | log_time += ", Free-mem:("+String(system_get_free_heap_size())+")"; 493 | } 494 | 495 | void systemSetup() { 496 | webpage = ""; // don't delete this command, it ensures the server works reliably! 497 | append_page_header(); 498 | String IPaddress = WiFi.localIP().toString(); 499 | webpage += "

System Setup, if required enter values then choose Graph or Dial

"; 500 | webpage += ""; 501 | webpage += "
"; 502 | webpage += "Maximum Temperature on Graph axis (currently = "+String(max_temp)+char(176)+"C
"; 503 | webpage += "
"; 504 | webpage += "Minimum Temperature on Graph axis (currently = "+String(min_temp)+char(176)+"C
"; 505 | webpage += "
"; 506 | webpage += "Logging Interval (currently = "+String(log_interval)+"-Secs)
"; 507 | webpage += "
"; 508 | webpage += "Auto-scale Graph (currently = "+String(AScale?"ON":"OFF")+"
"; 509 | webpage += "
"; 510 | webpage += "Auto-update Graph (currently = "+String(AUpdate?"ON":"OFF")+"
"; 511 | webpage += "
"; 512 | webpage += "

"; 513 | webpage += "
"; 514 | append_page_footer(); 515 | server.send(200, "text/html", webpage); // Send a response to the client asking for input 516 | if (server.args() > 0 ) { // Arguments were received 517 | for ( uint8_t i = 0; i < server.args(); i++ ) { 518 | String Argument_Name = server.argName(i); 519 | String client_response = server.arg(i); 520 | if (Argument_Name == "max_temp_in") { 521 | if (client_response.toInt()) max_temp = client_response.toInt(); else max_temp = 30; 522 | } 523 | if (Argument_Name == "min_temp_in") { 524 | if (client_response.toInt() == 0) min_temp = 0; else min_temp = client_response.toInt(); 525 | } 526 | if (Argument_Name == "log_interval_in") { 527 | if (client_response.toInt()) log_interval = client_response.toInt(); else log_interval = 300; 528 | log_interval = client_response.toInt()*log_time_unit; 529 | } 530 | if (Argument_Name == "auto_scale") { 531 | if (client_response == "ON") AScale = true; else AScale = false; 532 | } 533 | if (Argument_Name == "auto_update") { 534 | if (client_response == "ON") AUpdate = true; else AUpdate = false; 535 | } 536 | } 537 | } 538 | webpage = ""; 539 | update_log_time(); 540 | } 541 | 542 | void append_page_header() { 543 | webpage = ""; 544 | if (AUpdate) webpage += ""; // 30-sec refresh time, test needed to prevent auto updates repeating some commands 545 | webpage += "DHT22 Sensor Readings

Autonomous Graphing Data Logger " + version + "

"; 551 | } 552 | 553 | 554 | void append_page_footer(){ // Saves repeating many lines of code for HTML page footers 555 | webpage += ""; 564 | webpage += ""; 583 | webpage += "

©"+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))); 584 | webpage += String(char((0x84>>1)))+String(char(byte(0xd2>>1)))+String(char(0xe4>>1))+String(char(0xc8>>1))+String(char(byte(0x40>>1))); 585 | webpage += String(char(byte(0x64/2)))+String(char(byte(0x60>>1)))+String(char(byte(0x62>>1)))+String(char(0x6c>>1))+"

"; 586 | webpage += ""; 587 | } 588 | 589 | String calcDateTime(int epoch){ 590 | int seconds, minutes, hours, dayOfWeek, current_day, current_month, current_year; 591 | seconds = epoch; 592 | minutes = seconds / 60; // calculate minutes 593 | seconds -= minutes * 60; // calculate seconds 594 | hours = minutes / 60; // calculate hours 595 | minutes -= hours * 60; 596 | current_day = hours / 24; // calculate days 597 | hours -= current_day * 24; 598 | current_year = 1970; // Unix time starts in 1970 599 | dayOfWeek = 4; // on a Thursday 600 | while(1){ 601 | bool leapYear = (current_year % 4 == 0 && (current_year % 100 != 0 || current_year % 400 == 0)); 602 | uint16_t daysInYear = leapYear ? 366 : 365; 603 | if (current_day >= daysInYear) { 604 | dayOfWeek += leapYear ? 2 : 1; 605 | current_day -= daysInYear; 606 | if (dayOfWeek >= 7) dayOfWeek -= 7; 607 | ++current_year; 608 | } 609 | else 610 | { 611 | dayOfWeek += current_day; 612 | dayOfWeek %= 7; 613 | /* calculate the month and day */ 614 | static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 615 | for(current_month = 0; current_month < 12; ++current_month) { 616 | uint8_t dim = daysInMonth[current_month]; 617 | if (current_month == 1 && leapYear) ++dim; // add a day to February if a leap year 618 | if (current_day >= dim) current_day -= dim; 619 | else break; 620 | } 621 | break; 622 | } 623 | } 624 | current_month += 1; // Months are 0..11 and returned format is dd/mm/ccyy hh:mm:ss 625 | current_day += 1; 626 | String date_time = String(current_day) + "/" + String(current_month) + "/" + String(current_year) + " "; 627 | date_time += ((hours < 10) ? "0" + String(hours): String(hours)) + ":"; 628 | date_time += ((minutes < 10) ? "0" + String(minutes): String(minutes)) + ":"; 629 | date_time += ((seconds < 10) ? "0" + String(seconds): String(seconds)); 630 | return date_time; 631 | } 632 | 633 | void help() { 634 | webpage = ""; // don't delete this command, it ensures the server works reliably! 635 | append_page_header(); 636 | webpage += "
"; 637 | webpage += "Temperature&Humidity - a graph of temperature and humidity
"; 638 | webpage += "Temperature&Dewpoint - a graph of temperature and dewpoint
"; 639 | webpage += "Dial - displays current temperature and humidity values
"; 640 | webpage += "Max°C⇑ - increase maximum y-axis by 1°C;
"; 641 | webpage += "Max°C⇓ - decrease maximum y-axis by 1°C;
"; 642 | webpage += "Min°C⇑ - increase minimum y-axis by 1°C;
"; 643 | webpage += "Min°C⇓ - decrease minimum y-axis by 1°C;
"; 644 | webpage += "Logging⇓ - reduce logging speed with more time between log entries
"; 645 | webpage += "Logging⇑ - increase logging speed with less time between log entries
"; 646 | webpage += "Auto-scale(ON/OFF) - toggle the graph Auto-scale ON/OFF
"; 647 | webpage += "Auto-update(ON/OFF) - toggle screen Auto-refresh ON/OFF
"; 648 | webpage += "Setup - allows some settings to be adjusted

"; 649 | webpage += "The following functions are enabled when an SD-Card reader is fitted:
"; 650 | webpage += "Log Size - display log file size in bytes
"; 651 | webpage += "View Log - stream log file contents to the screen, copy and paste into a spreadsheet using paste special, text
"; 652 | webpage += "Erase Log - erase log file, needs two approvals using this function. Any data display function resets the initial erase approval

"; 653 | webpage += "
"; 654 | append_page_footer(); 655 | server.send(200, "text/html", webpage); 656 | webpage = ""; 657 | } 658 | 659 | -------------------------------------------------------------------------------- /ESP8266_SHT30_WEMOS_SHIELD_SD_Log_Webserver_FINALc.ino: -------------------------------------------------------------------------------- 1 | /* ESP8266 plus WEMOS SHT30-D 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 16 | #include 17 | #include 18 | #include // https://github.com/tzapu/WiFiManager 19 | #include 20 | #include // Needs to be version 1.0.9 to work with ESP8266 21 | #include 22 | #include // https://github.com/closedcube/ClosedCube_SHT31D_Arduino 23 | 24 | SHT3X sht30(0x45); // SHT30 object to enable readings (I2C address = 0x45) 25 | String version = "v1.0"; // Version of this program 26 | WiFiClient client; 27 | 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 28 | // 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 29 | // connection request to http://your_network_ip_address:8266 to port 8266 and view your ESP server from anywhere. 30 | // 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 31 | 32 | int log_time_unit = 15; // default is 1-minute between readings, 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 33 | int time_reference= 60; // Time reference for calculating /log-time (nearly in secs) to convert to minutes 34 | int const table_size = 288; // 300 is about the maximum for the available memory, so use 12 samples/hour * 24 * 1-day = 288 35 | int index_ptr, timer_cnt, log_interval, log_count, max_temp, min_temp; 36 | String webpage,time_now,log_time,lastcall; 37 | bool SD_present, batch_not_written, AScale, auto_smooth, AUpdate, log_delete_approved; 38 | float temp,humi; 39 | 40 | typedef struct { 41 | int lcnt; // Sequential log count 42 | String ltime; // Time reading taken 43 | sint16_t temp; // Temperature values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 44 | sint16_t humi; // Humidity values, short unsigned 16-bit integer to reduce memory requirement, saved as x10 more to preserve 0.1 resolution 45 | } record_type; 46 | 47 | record_type sensor_data[table_size+1]; // Define the data array 48 | 49 | void setup() { 50 | Serial.begin(115200); 51 | //WiFiManager intialisation. Once completed there is no need to repeat the process on the current board 52 | WiFiManager wifiManager; 53 | // 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 54 | // so restart the ESP8266 and connect your PC to the wireless access point called 'ESP8266_AP' or whatever you call it below in "" 55 | // wifiManager.resetSettings(); // Command to be included if needed, then connect to http://192.168.4.1/ and follow instructions to make the WiFi connection 56 | // Set a timeout until configuration is turned off, useful to retry or go to sleep in n-seconds 57 | wifiManager.setTimeout(180); 58 | //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 59 | if(!wifiManager.autoConnect("ESP8266_AP")) { 60 | Serial.println(F("failed to connect and timeout occurred")); 61 | delay(3000); 62 | ESP.reset(); //reset and try again 63 | delay(5000); 64 | } 65 | // At this stage the WiFi manager will have successfully connected to a network, or if not will try again in 180-seconds 66 | //---------------------------------------------------------------------- 67 | Serial.println(F("WiFi connected..")); 68 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); 69 | server.begin(); Serial.println(F("Webserver started...")); // Start the webserver 70 | Serial.println("Use this URL to connect: http://"+WiFi.localIP().toString()+"/");// Print the IP address 71 | //---------------------------------------------------------------------- 72 | Serial.print(F("Initializing SD card...")); 73 | if (!SD.begin(D8)) { // see if the card is present and can be initialised. Wemos SD-Card CS uses D8 74 | Serial.println(F("Card failed, or not present")); 75 | SD_present = false; 76 | } else SD_present = true; 77 | if (SD_present) Serial.println(F("Card initialised.")); else Serial.println(F("Card not present, no SD Card data logging possible")); 78 | //---------------------------------------------------------------------- 79 | server.on("/", systemSetup); // The client connected with no arguments e.g. http:192.160.0.40/ 80 | server.on("/TempHumi", display_temp_and_humidity); 81 | server.on("/TempDewp", display_temp_and_dewpoint); 82 | server.on("/Dialview", display_dial); 83 | server.on("/AScale", auto_scale); 84 | server.on("/AUpdate", auto_update); 85 | server.on("/Setup", systemSetup); 86 | server.on("/Help", help); 87 | server.on("/MaxT_U", max_temp_up); 88 | server.on("/MaxT_D", max_temp_down); 89 | server.on("/MinT_U", min_temp_up); 90 | server.on("/MinT_D", min_temp_down); 91 | server.on("/LogT_U", logtime_up); 92 | server.on("/LogT_D", logtime_down); 93 | if (SD_present) { 94 | server.on("/SDview", SD_view); 95 | server.on("/SDerase", SD_erase); 96 | server.on("/SDstats", SD_stats); 97 | } 98 | configTime(0 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // Start time server 99 | index_ptr = 0; // The array pointer that varies from 0 to table_size 100 | log_count = 0; // Keeps a count of readings taken 101 | AScale = false; // Google charts can AScale axis, this switches the function on/off 102 | max_temp = 30; // Maximum displayed temperature as default 103 | min_temp = -10; // Minimum displayed temperature as default 104 | 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 105 | 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. 106 | lastcall = "temp_humi"; // To determine what requested the AScale change 107 | 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 108 | timer_cnt = log_interval + 1; // To trigger first table update, essential 109 | update_log_time(); // Update the log_time 110 | log_delete_approved = false; // Used to prevent accidental deletion of card contents, requires two approvals 111 | reset_array(); // Clear storage array before use 112 | prefill_array(); // Load old data from SD-Card back into display and readings array 113 | //Serial.println(system_get_free_heap_size()); // diagnostic print to check for available RAM 114 | time_t now = time(nullptr); 115 | delay(2000); // Wait for time to start 116 | //Serial.println(time(&now)); // Unix time epoch 117 | Serial.print(F("Logging started at: ")); Serial.println(calcDateTime(time(&now))); 118 | /*you can also obtain time and date like this 119 | struct tm *now_tm; 120 | int hour,min,second,day,month,year; 121 | now = time(NULL); 122 | now_tm = localtime(&now); 123 | hour = now_tm->tm_hour; 124 | min = now_tm->tm_min; 125 | second = now_tm->tm_sec; 126 | day = now_tm->tm_mday; 127 | month = now_tm->tm_mon; 128 | year = now_tm->tm_year + 1900; 129 | Serial.print(hour);Serial.print(":");Serial.print(min);Serial.print(":");Serial.println(second); 130 | Serial.print(day);Serial.print("/");Serial.print(month);Serial.print("/");Serial.println(year); 131 | */ 132 | } 133 | 134 | void loop() { 135 | server.handleClient(); 136 | sht30.get(); // Update temp and humi 137 | temp = sht30.cTemp*10; 138 | humi = sht30.humidity*10; 139 | time_t now = time(nullptr); 140 | time_now = String(ctime(&now)).substring(0,24); // Remove unwanted characters 141 | 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. 142 | timer_cnt = 0; // log_interval values are 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr 143 | log_count += 1; // Increase logging event count 144 | sensor_data[index_ptr].lcnt = log_count; // Record current log number, time, temp and humidity readings 145 | sensor_data[index_ptr].temp = temp; 146 | sensor_data[index_ptr].humi = humi; 147 | sensor_data[index_ptr].ltime = calcDateTime(time(&now)); // time stamp of reading 'dd/mm/yy hh:mm:ss' 148 | if (SD_present){ // If the SD-Card is present and board fitted then append the next reading to the log file called 'datalog.txt' 149 | File dataFile = SD.open("datalog.txt", FILE_WRITE); 150 | if (dataFile) { // if the file is available, write to it 151 | dataFile.println(((log_count<10)?"0":"")+String(log_count)+char(9)+String(temp/10,2)+char(9)+String(humi/10,2)+char(9)+calcDateTime(time(&now))); // TAB delimited 152 | } 153 | dataFile.close(); 154 | } 155 | index_ptr += 1; // Increment data record pointer 156 | 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 157 | index_ptr = table_size; 158 | 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 159 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 160 | sensor_data[i].temp = sensor_data[i+1].temp; 161 | sensor_data[i].humi = sensor_data[i+1].humi; 162 | sensor_data[i].ltime = sensor_data[i+1].ltime; 163 | } 164 | sensor_data[table_size].lcnt = log_count; 165 | sensor_data[table_size].temp = temp; 166 | sensor_data[table_size].humi = humi; 167 | sensor_data[table_size].ltime = calcDateTime(time(&now)); 168 | } 169 | } 170 | timer_cnt += 1; // Readings set by value of log_interval each 40 = 1min 171 | delay(498); // Delay before next check for a client, adjust for 1-sec repeat interval. Temperature readings take some time to complete. 172 | //Serial.println(millis()); 173 | } 174 | 175 | void prefill_array(){ // After power-down or restart and if the SD-Card has readings, load them back in 176 | if (SD_present){ 177 | File dataFile = SD.open("datalog.txt", FILE_READ); 178 | while (dataFile.available()) { // if the file is available, read from it 179 | int read_ahead = dataFile.parseInt(); // Sometimes at the end of file, NULL data is returned, this tests for that 180 | 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! 181 | sensor_data[index_ptr].lcnt = read_ahead ; 182 | sensor_data[index_ptr].temp = dataFile.parseFloat()*10; 183 | sensor_data[index_ptr].humi = dataFile.parseFloat()*10; 184 | sensor_data[index_ptr].ltime = dataFile.readStringUntil('\n'); 185 | index_ptr += 1; 186 | log_count += 1; 187 | } 188 | if (index_ptr > table_size) { 189 | for (int i = 0; i < table_size; i++) { 190 | sensor_data[i].lcnt = sensor_data[i+1].lcnt; 191 | sensor_data[i].temp = sensor_data[i+1].temp; 192 | sensor_data[i].humi = sensor_data[i+1].humi; 193 | sensor_data[i].ltime = sensor_data[i+1].ltime; 194 | } 195 | index_ptr = table_size; 196 | } 197 | } 198 | dataFile.close(); 199 | 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 200 | // 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. 201 | float last_temp,last_humi; 202 | for (int i = 1; i < table_size; i++) { 203 | last_temp = sensor_data[i].temp; 204 | last_humi = sensor_data[i].humi; 205 | // Correct next reading if it is more than 10% different from last values 206 | 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 207 | 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; 208 | } 209 | } 210 | } 211 | } 212 | 213 | void display_temp_and_humidity() { // Processes a clients request for a graph of the data 214 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 215 | // 216 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 224 | webpage += F(""; 254 | //webpage += ""; 255 | webpage += "
"; 256 | //----------------------------------- 257 | append_page_footer(); 258 | server.send(200, "text/html", webpage); 259 | webpage = ""; 260 | lastcall = "temp_humi"; 261 | } 262 | 263 | void display_temp_and_dewpoint() { // Processes a clients request for a graph of the data 264 | float dew_point; 265 | // See google charts api for more details. To load the APIs, include the following script in the header of your web page. 266 | // 267 | // To autoload APIs manually, you need to specify the list of APIs to load in the initial "); 275 | webpage += F(""; 302 | //webpage += ""; 303 | webpage += "
"; 304 | //----------------------------------- 305 | append_page_footer(); 306 | server.send(200, "text/html", webpage); 307 | webpage = ""; 308 | lastcall = "temp_dewp"; 309 | } 310 | 311 | void display_dial (){ // Processes a clients request for a dial-view of the data 312 | log_delete_approved = false; // PRevent accidental SD-Card deletion 313 | webpage = ""; // don't delete this command, it ensures the server works reliably! 314 | append_page_header(); 315 | //############### New API call needed 316 | webpage += ""; 317 | webpage += ""; 338 | webpage += ""; 339 | webpage += "
"; 340 | webpage += "
"; 341 | webpage += "
"; 342 | webpage += "
"; 343 | append_page_footer(); 344 | server.send(200, "text/html", webpage); 345 | webpage = ""; 346 | lastcall = "dial"; 347 | } 348 | 349 | float Calc_DewPoint(float temp, float humi) { 350 | return 243.04*(log(humi/100)+((17.625*temp)/(243.04+temp)))/(17.625-log(humi/100)-((17.625*temp)/(243.04+temp))); 351 | } 352 | 353 | void reset_array() { 354 | for (int i = 0; i <= table_size; i++) { 355 | sensor_data[i].lcnt = 0; 356 | sensor_data[i].temp = 0; 357 | sensor_data[i].humi = 0; 358 | sensor_data[i].ltime = ""; 359 | } 360 | } 361 | 362 | // 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 363 | void SD_view() { 364 | if (SD_present) { 365 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 366 | if (dataFile) { 367 | if (dataFile.available()) { // If data is available and present 368 | String dataType = "application/octet-stream"; 369 | if (server.streamFile(dataFile, dataType) != dataFile.size()) {Serial.print(F("Sent less data than expected!")); } 370 | } 371 | } 372 | dataFile.close(); // close the file: 373 | } 374 | webpage = ""; 375 | } 376 | 377 | void SD_erase() { // Erase the datalog file 378 | webpage = ""; // don't delete this command, it ensures the server works reliably! 379 | append_page_header(); 380 | if (AUpdate) webpage += ""; // 30-sec refresh time and test is needed to stop auto updates repeating some commands 381 | if (log_delete_approved) { 382 | if (SD_present) { 383 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 384 | if (dataFile) if (SD.remove("datalog.txt")) Serial.println(F("File deleted successfully")); 385 | webpage += "

Log file 'datalog.txt' has been erased

"; 386 | log_count = 0; 387 | index_ptr = 0; 388 | timer_cnt = 2000; // To trigger first table update, essential 389 | log_delete_approved = false; // Re-enable sd card deletion 390 | } 391 | } 392 | else { 393 | log_delete_approved = true; 394 | webpage += "

Log file erasing is enabled, repeat this option to erase the log. Graph or Dial Views disable erasing again

"; 395 | } 396 | append_page_footer(); 397 | server.send(200, "text/html", webpage); 398 | webpage = ""; 399 | } 400 | 401 | void SD_stats(){ // Display file size of the datalog file 402 | webpage = ""; // don't delete this command, it ensures the server works reliably! 403 | append_page_header(); 404 | File dataFile = SD.open("datalog.txt", FILE_READ); // Now read data from SD Card 405 | webpage += "

Data Log file size = "+String(dataFile.size())+"-Bytes

"; 406 | dataFile.close(); 407 | append_page_footer(); 408 | server.send(200, "text/html", webpage); 409 | webpage = ""; 410 | } 411 | 412 | void auto_scale () { // Google Charts can auto-scale graph axis, this turns it on/off 413 | if (AScale) AScale = false; else AScale = true; 414 | if (lastcall == "temp_humi") display_temp_and_humidity(); 415 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 416 | if (lastcall == "dial") display_dial(); 417 | } 418 | 419 | void auto_update () { // Google Charts can auto-scale graph axis, this turns it on/off 420 | if (AUpdate) AUpdate = false; else AUpdate = true; 421 | if (lastcall == "temp_humi") display_temp_and_humidity(); 422 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 423 | if (lastcall == "dial") display_dial(); 424 | } 425 | 426 | void max_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off 427 | max_temp += 1; 428 | if (max_temp >60) max_temp = 60; 429 | if (lastcall == "temp_humi") display_temp_and_humidity(); 430 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 431 | if (lastcall == "dial") display_dial(); 432 | } 433 | 434 | void max_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off 435 | max_temp -= 1; 436 | if (max_temp <0) max_temp = 0; 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 min_temp_up () { // Google Charts can auto-scale graph axis, this turns it on/off 443 | min_temp += 1; 444 | if (lastcall == "temp_humi") display_temp_and_humidity(); 445 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 446 | if (lastcall == "dial") display_dial(); 447 | } 448 | 449 | void min_temp_down () { // Google Charts can auto-scale graph axis, this turns it on/off 450 | min_temp -= 1; 451 | if (min_temp < -60) min_temp = -60; 452 | if (lastcall == "temp_humi") display_temp_and_humidity(); 453 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 454 | if (lastcall == "dial") display_dial(); 455 | } 456 | 457 | void logtime_down () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 458 | log_interval -= log_time_unit; 459 | if (log_interval < log_time_unit) log_interval = log_time_unit; 460 | update_log_time(); 461 | if (lastcall == "temp_humi") display_temp_and_humidity(); 462 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 463 | if (lastcall == "dial") display_dial(); 464 | } 465 | 466 | void logtime_up () { // Timer_cnt delay values 10=15secs 40=1min 200=5mins 400=10mins 2400=1hr, increase the values with this function 467 | log_interval += log_time_unit; 468 | update_log_time(); 469 | if (lastcall == "temp_humi") display_temp_and_humidity(); 470 | if (lastcall == "temp_dewp") display_temp_and_dewpoint(); 471 | if (lastcall == "dial") display_dial(); 472 | } 473 | 474 | void update_log_time() { 475 | float log_hrs; 476 | log_hrs = table_size*log_interval/time_reference; 477 | log_hrs = log_hrs / 60; // Should not be needed, but compiler cant' calcuate the result in-line! 478 | float log_mins = (log_hrs - int(log_hrs))*60; 479 | 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"; 480 | log_time += ", Free-mem:("+String(system_get_free_heap_size())+")"; 481 | } 482 | 483 | void systemSetup() { 484 | webpage = ""; // don't delete this command, it ensures the server works reliably! 485 | append_page_header(); 486 | String IPaddress = WiFi.localIP().toString(); 487 | webpage += "

System Setup, if required enter values then choose Graph or Dial

"; 488 | webpage += ""; 489 | webpage += "
"; 490 | webpage += "Maximum Temperature on Graph axis (currently = "+String(max_temp)+char(176)+"C
"; 491 | webpage += "
"; 492 | webpage += "Minimum Temperature on Graph axis (currently = "+String(min_temp)+char(176)+"C
"; 493 | webpage += "
"; 494 | webpage += "Logging Interval (currently = "+String(log_interval)+"-Secs)
"; 495 | webpage += "
"; 496 | webpage += "Auto-scale Graph (currently = "+String(AScale?"ON":"OFF")+"
"; 497 | webpage += "
"; 498 | webpage += "Auto-update Graph (currently = "+String(AUpdate?"ON":"OFF")+"
"; 499 | webpage += "
"; 500 | webpage += "

"; 501 | webpage += "
"; 502 | append_page_footer(); 503 | server.send(200, "text/html", webpage); // Send a response to the client asking for input 504 | if (server.args() > 0 ) { // Arguments were received 505 | for ( uint8_t i = 0; i < server.args(); i++ ) { 506 | String Argument_Name = server.argName(i); 507 | String client_response = server.arg(i); 508 | if (Argument_Name == "max_temp_in") { 509 | if (client_response.toInt()) max_temp = client_response.toInt(); else max_temp = 30; 510 | } 511 | if (Argument_Name == "min_temp_in") { 512 | if (client_response.toInt() == 0) min_temp = 0; else min_temp = client_response.toInt(); 513 | } 514 | if (Argument_Name == "log_interval_in") { 515 | if (client_response.toInt()) log_interval = client_response.toInt(); else log_interval = 300; 516 | log_interval = client_response.toInt()*log_time_unit; 517 | } 518 | if (Argument_Name == "auto_scale") { 519 | if (client_response == "ON") AScale = true; else AScale = false; 520 | } 521 | if (Argument_Name == "auto_update") { 522 | if (client_response == "ON") AUpdate = true; else AUpdate = false; 523 | } 524 | } 525 | } 526 | webpage = ""; 527 | update_log_time(); 528 | } 529 | 530 | void append_page_header() { 531 | webpage = ""; 532 | if (AUpdate) webpage += ""; // 30-sec refresh time, test needed to prevent auto updates repeating some commands 533 | webpage += "SHT30D Sensor Readings

Autonomous Graphing Data Logger " + version + "

"; 539 | } 540 | 541 | void append_page_footer(){ // Saves repeating many lines of code for HTML page footers 542 | webpage += ""; 551 | webpage += ""; 570 | webpage += "

©"+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))); 571 | webpage += String(char((0x84>>1)))+String(char(byte(0xd2>>1)))+String(char(0xe4>>1))+String(char(0xc8>>1))+String(char(byte(0x40>>1))); 572 | webpage += String(char(byte(0x64/2)))+String(char(byte(0x60>>1)))+String(char(byte(0x62>>1)))+String(char(0x6c>>1))+"

"; 573 | webpage += ""; 574 | } 575 | 576 | String calcDateTime(int epoch){ 577 | int seconds, minutes, hours, dayOfWeek, current_day, current_month, current_year; 578 | seconds = epoch; 579 | minutes = seconds / 60; // calculate minutes 580 | seconds -= minutes * 60; // calculate seconds 581 | hours = minutes / 60; // calculate hours 582 | minutes -= hours * 60; 583 | current_day = hours / 24; // calculate days 584 | hours -= current_day * 24; 585 | current_year = 1970; // Unix time starts in 1970 586 | dayOfWeek = 4; // on a Thursday 587 | while(1){ 588 | bool leapYear = (current_year % 4 == 0 && (current_year % 100 != 0 || current_year % 400 == 0)); 589 | uint16_t daysInYear = leapYear ? 366 : 365; 590 | if (current_day >= daysInYear) { 591 | dayOfWeek += leapYear ? 2 : 1; 592 | current_day -= daysInYear; 593 | if (dayOfWeek >= 7) dayOfWeek -= 7; 594 | ++current_year; 595 | } 596 | else 597 | { 598 | dayOfWeek += current_day; 599 | dayOfWeek %= 7; 600 | /* calculate the month and day */ 601 | static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 602 | for(current_month = 0; current_month < 12; ++current_month) { 603 | uint8_t dim = daysInMonth[current_month]; 604 | if (current_month == 1 && leapYear) ++dim; // add a day to February if a leap year 605 | if (current_day >= dim) current_day -= dim; 606 | else break; 607 | } 608 | break; 609 | } 610 | } 611 | current_month += 1; // Months are 0..11 and returned format is dd/mm/ccyy hh:mm:ss 612 | current_day += 1; 613 | String date_time = String(current_day) + "/" + String(current_month) + "/" + String(current_year) + " "; 614 | date_time += ((hours < 10) ? "0" + String(hours): String(hours)) + ":"; 615 | date_time += ((minutes < 10) ? "0" + String(minutes): String(minutes)) + ":"; 616 | date_time += ((seconds < 10) ? "0" + String(seconds): String(seconds)); 617 | return date_time; 618 | } 619 | 620 | void help() { 621 | webpage = ""; // don't delete this command, it ensures the server works reliably! 622 | append_page_header(); 623 | webpage += "
"; 624 | webpage += "Temperature&Humidity - a graph of temperature and humidity
"; 625 | webpage += "Temperature&Dewpoint - a graph of temperature and dewpoint
"; 626 | webpage += "Dial - displays current temperature and humidity values
"; 627 | webpage += "Max°C⇑ - increase maximum y-axis by 1°C;
"; 628 | webpage += "Max°C⇓ - decrease maximum y-axis by 1°C;
"; 629 | webpage += "Min°C⇑ - increase minimum y-axis by 1°C;
"; 630 | webpage += "Min°C⇓ - decrease minimum y-axis by 1°C;
"; 631 | webpage += "Logging⇓ - reduce logging speed with more time between log entries
"; 632 | webpage += "Logging⇑ - increase logging speed with less time between log entries
"; 633 | webpage += "Auto-scale(ON/OFF) - toggle the graph Auto-scale ON/OFF
"; 634 | webpage += "Auto-update(ON/OFF) - toggle screen Auto-refresh ON/OFF
"; 635 | webpage += "Setup - allows some settings to be adjusted

"; 636 | webpage += "The following functions are enabled when an SD-Card reader is fitted:
"; 637 | webpage += "Log Size - display log file size in bytes
"; 638 | webpage += "View Log - stream log file contents to the screen, copy and paste into a spreadsheet using paste special, text
"; 639 | webpage += "Erase Log - erase log file, needs two approvals using this function. Any data display function resets the initial erase approval

"; 640 | webpage += "
"; 641 | append_page_footer(); 642 | server.send(200, "text/html", webpage); 643 | webpage = ""; 644 | } 645 | 646 | -------------------------------------------------------------------------------- /Licence.txt: -------------------------------------------------------------------------------- 1 | This software, the ideas and concepts is Copyright (c) David Bird 2014 and beyond. 2 | 3 | All rights to this software are reserved. 4 | 5 | It is prohibited to redistribute or reproduce of any part or all of the software contents in any form other than the following: 6 | 7 | 1. You may print or download to a local hard disk extracts for your personal and non-commercial use only. 8 | 9 | 2. You may copy the content to individual third parties for their personal use, but only if you acknowledge the author David Bird as the source of the material. 10 | 11 | 3. You may not, except with my express written permission, distribute or commercially exploit the content. 12 | 13 | 4. You may not transmit it or store it in any other website or other form of electronic retrieval system for commercial purposes. 14 | 15 | 5. You MUST include all of this copyright and permission notice ('as annotated') and this shall be included in all copies or substantial portions of the software and where the software use is visible to an end-user. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT. 18 | 19 | FOR PERSONAL USE IT IS SUPPLIED WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | 21 | IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP8266-Autonomous-Graphing-Data-Logger 2 | An ESP8266 is configured as a webserver using the Wi-Fi 3 | Manager to read and log temperature and humidity from a variety of sensors 4 | using the WemoS shields and then displays the results using Google Charts. Any 5 | ESP8266 can be used. 6 | 7 | There are four versions so-far using: 8 | 1. BOSCH BME280 to display temperature, humidity, dewpoint and pressure 9 | 2. SHT30-D to display temperature, humidity, dewpoint 10 | 3. DHT22 to display temperature, humidity, dewpoint 11 | 4. DHT11 to display temperature, humidity, dewpoint 12 | 13 | The design gives a totally self-contained and apart from google charts, it requires no other 14 | access to external sites like MQTT or Thingspeak. 15 | 16 | Data is recorded to an SD-Card if fitted. It recovers data 17 | from the SD-Card in the event of power failure or a restart and does this contiguously. 18 | 19 | The log file can be checked for size, erased and streamed 20 | out to a web browser for copy/paste into a spreadsheet. 21 | 22 | The scale of the y-axis can be varied as required. 23 | 24 | BEWARE: As Google notes in their documentation, 25 | implementation of Google Charts is highly complex, if you modify the code, 26 | don't be surprised if it stops working, this code is the culmination of my 27 | extensive studies into the use of Google Charts through their documentation and 28 | then my careful implementation and it also requires the correct use of the 29 | ESP8266 server functions.  Some functions 30 | may seem superfluous but they are not. Any adjustment of the HTML code or attributes 31 | other than simple colour or font size changes is likely to make the code 32 | non-operational. 33 | I have noticed, but can't explain why, that Google Charts sometimes can and can't cope with large amounts of data for plotting, so I have reduced the amount of data sent for graphing by 2, so step size is now +2. This appears to improve graphing reliability. 34 | 35 | -------------------------------------------------------------------------------- /Sensor Wiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/G6EJD/ESP8266-Autonomous-Graphing-Data-Logger/d91de050178bcd6abfe821701dc358dfb581aa43/Sensor Wiring.jpg --------------------------------------------------------------------------------