├── 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 += "";
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 ReadingsAutonomous 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 += "
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 += "";
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 = "