An error occurred while loading the file. Please try again.
web_server.cpp 9.80 KiB
#include "web_server.h"
//TODO: Add link to repos
namespace config {
  // Values should be defined in config.h
#ifdef HTTP_USER
  const char *http_user = HTTP_USER;
#else
  const char *http_user = "";
#endif
#ifdef HTTP_PASSWORD
  const char *http_password = HTTP_PASSWORD;
#else
  const char *http_password = "";
#endif
namespace web_server {
  const char *header_template;
  const char *body_template;
  const char *script_template;
  void handleWebServerRoot();
  void handleWebServerCSV();
  void handlePageNotFound();
  void handleDeleteCSV();
#if defined(ESP8266)
  ESP8266WebServer http(80); // Create a webserver object that listens for HTTP request on port 80
#elif defined(ESP32)
  WebServer http(80);
#endif
  void update() {
    http.handleClient(); // Listen for HTTP requests from clients
  void initialize() {
    header_template =
        PSTR("<!doctype html><html lang=en>"
            "<head>\n"
            "<title>%d ppm - CO2 SENSOR - %s - %s</title>\n"
            "<meta charset='UTF-8'>"
            // HfT Favicon
            "<link rel='icon' type='image/png' sizes='16x16' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAHtJREFUOE9jvMnA+5+BAsBIFQMkl85h+P3kGcOb8jqwW+TPH2H4de0GA29UGNxtfx49YWCRk0HwHz5iuKegwwB2AS4DkA2F6VR6cAWsEQbgBqDY9vARw/ejJ+Au+LxsFcPz6BSwHpwGYPMCSS6gyAAKYhESiKMGjPgwAADopHVhn5ynEwAAAABJRU5ErkJggg=='/>"
            // Responsive grid:
            "<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.3/build/pure-min.css'>\n"
            "<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.3/build/grids-responsive-min.css'>\n"
            // JS Graphs:
            "<script src='https://cdn.plot.ly/plotly-basic-1.58.2.min.js'></script>\n"
            // Fullscreen
            "<meta name='viewport' content='width=device-width, initial-scale=1'>\n"
            // Refresh after every measurement.
            // "<meta http-equiv='refresh' content='%d'>\n"
            "</head>\n"
            "<body>\n"
            "<div class='pure-g'><div class='pure-u-1'><div class='pure-menu'><p class='pure-menu-heading'>HfT-Stuttgart CO<sub>2</sub> sensor</p></div></div>\n"
            "<div class='pure-u-1'><ul class='pure-menu pure-menu-horizontal pure-menu-list'>\n"
            "<li class='pure-menu-item'><a href='#graph' class='pure-menu-link'>Graph</a></li>\n"
            "<li class='pure-menu-item'><a href='#table' class='pure-menu-link'>Info</a></li>\n"
            "<li class='pure-menu-item'><a href='#log' class='pure-menu-link'>Log</a></li>\n"
            "<li class='pure-menu-item'><a href='./%s' class='pure-menu-link'>Download CSV</a></li>\n"
            "</ul></div></div>\n");
    body_template =
        PSTR("<div class='pure-g'>\n"
            "<div class='pure-u-1' id='graph'></div>\n" // Graph placeholder
"</div>\n" "<div class='pure-g'>\n" //Sensor table "<table id='table' class='pure-table-striped pure-u-1 pure-u-md-1-2'>\n" "<tr><th>Sensor</th><th>%s</th></tr>\n" "<tr><td>CO<sub>2</sub> concentration</td><td>%5d ppm</td></tr>\n" "<tr><td>Temperature</td><td>%.1f&#8451;</td></tr>\n" "<tr><td>Humidity</td><td>%.1f%%</td></tr>\n" "<tr><td>Last measurement</td><td>%s</td></tr>\n" "<tr><td>Measurement timestep</td><td>%5d s</td></tr>\n" "<tr><td>Last CSV write</td><td>%s</td></tr>\n" "<tr><td>CSV timestep</td><td>%5d s</td></tr>\n" #ifdef MQTT "<tr><td>Last MQTT publish</td><td>%s</td></tr>\n" "<tr><td>MQTT publish timestep</td><td>%5d s</td></tr>\n" #endif "<tr><td>Temperature offset</td><td>%.1fK</td></tr>\n" "<tr><td>Local address</td><td><a href='http://%s.local/'>%s.local</a></td></tr>\n" "<tr><td>Local IP</td><td><a href='http://%s'>%s</a></td></tr>\n" "<tr><td>Free heap space</td><td>%6d bytes</td></tr>\n" "<tr><td>Available drive space</td><td>%d kB</td></tr>\n" "<tr><td>Max loop duration</td><td>%5d ms</td></tr>\n" "<tr><td>Board</td><td>%s</td></tr>\n" "<tr><td>Uptime</td><td>%4d h %02d min %02d s</td></tr>\n" "</table>\n" // CSV placeholder "<div id='log' class='pure-u-1 pure-u-md-1-2'></div>\n" "<form action='/delete_csv' method='POST' onsubmit=\"return confirm('Are you really sure you want to delete all data?') && (document.body.style.cursor = 'wait');\">" "<input type='submit' value='Delete CSV'/>" "</form>\n" "</div>\n"); script_template = PSTR("<script>\n" "document.body.style.cursor = 'default';\n" "fetch('./%s',{credentials:'include'})\n" // Get CSV, fill table and fill diagram ".then(response=>response.text())\n" ".then(csvText=>csvToTable(csvText))\n" ".then(htmlTable=>addLogTableToPage(htmlTable))\n" ".then(_=>Plotly.newPlot('graph',data,layout,{displaylogo:false}))\n" ".catch(e=>console.error(e));\n" "xs=[];\n" "data=[{x:xs,y:[],type:'scatter',name:'CO<sub>2</sub>',line:{color:'#2ca02c'}},\n" "{x:xs,y:[],type:'scatter',name:'Temperature',yaxis:'y2',line:{color:'#ff7f0e',dash:'dot'}},\n" "{x:xs,y:[],type:'scatter',name:'Humidity',yaxis:'y3',line:{color:'#1f77b4',dash:'dot'}}];\n" "layout={height:600,title:'%s',legend:{xanchor:'right',x:0.2,y:1.0},\n" "xaxis:{domain:[0.0,0.85]},yaxis:{ticksuffix:'ppm',range:[0,2000],dtick:200},\n" "yaxis2:{overlaying:'y',side:'right',ticksuffix:'°C',position:0.9,anchor:'free',range:[0,30],dtick:3},\n" "yaxis3:{overlaying:'y',side:'right',ticksuffix:'%%',position:0.95,anchor:'free',range:[0,100],dtick:10}\n" "};\n" "function csvToTable(csvText) {\n" "csvText=csvText.trim();\n" "lines=csvText.split('\\n');\n" "table=document.createElement('table');\n" "table.className='pure-table-striped';\n" "n=lines.length;\n" "lines.forEach((line,i)=>{\n" "fields=line.split(';');\n" "xs.push(fields[0]);\n" "data[0]['y'].push(fields[1]);\n" "data[1]['y'].push(fields[2]);\n" "data[2]['y'].push(fields[3]);\n" "if(i>4 && i<n-12){if(i==5){fields=['...','...','...','...']}else{return;}}\n" "row=document.createElement('tr');\n" "fields.forEach((field,index)=>{\n" "cell=document.createElement(i<2?'th':'td');\n" "cell.appendChild(document.createTextNode(field));\n" "row.appendChild(cell);});\n" "table.appendChild(row);});\n" "return table;}\n"
"function addLogTableToPage(table){document.getElementById('log').appendChild(table);}\n" "</script>\n" "</body>\n" "</html>"); // Web-server http.on("/", handleWebServerRoot); http.on("/" + csv_writer::filename, handleWebServerCSV); http.on("/delete_csv", HTTP_POST, handleDeleteCSV); http.onNotFound(handlePageNotFound); http.begin(); Serial.print(F("You can access this sensor via http://")); Serial.print(SENSOR_ID); Serial.print(F(".local (might be unstable) or http://")); Serial.println(WiFi.localIP()); } // Allow access if http_user or http_password are empty, or if provided credentials match bool shouldBeAllowed() { return strcmp(config::http_user, "") == 0 || strcmp(config::http_password, "") == 0 || http.authenticate(config::http_user, config::http_password); } void handleWebServerRoot() { if (!shouldBeAllowed()) { return http.requestAuthentication(DIGEST_AUTH); } unsigned long ss = seconds(); unsigned int hh = ss / 3600; ss -= hh * 3600; uint8_t mm = ss / 60; ss -= mm * 60; uint16_t available_fs_space = csv_writer::getAvailableSpace() / 1024; //NOTE: Splitting in multiple parts in order to use less RAM char content[2000]; // Update if needed // Header size : 1383 - Body size : 1246 - Script size : 1648 // Header snprintf_P(content, sizeof(content), header_template, sensor::co2, SENSOR_ID.c_str(), WiFi.localIP().toString().c_str(), csv_writer::filename.c_str()); http.setContentLength(CONTENT_LENGTH_UNKNOWN); http.send_P(200, PSTR("text/html"), content); // Body snprintf_P(content, sizeof(content), body_template, SENSOR_ID.c_str(), sensor::co2, sensor::temperature, sensor::humidity, sensor::timestamp.c_str(), config::measurement_timestep, csv_writer::last_successful_write.c_str(), config::csv_interval, #ifdef MQTT mqtt::last_successful_publish.c_str(), config::sending_interval, #endif config::temperature_offset, SENSOR_ID.c_str(), SENSOR_ID.c_str(), WiFi.localIP().toString().c_str(), WiFi.localIP().toString().c_str(), get_free_heap_size(), available_fs_space, max_loop_duration, BOARD, hh, mm, ss); http.sendContent(content); // Script snprintf_P(content, sizeof(content), script_template, csv_writer::filename.c_str(), SENSOR_ID.c_str()); http.sendContent(content); } void handleWebServerCSV() { if (!shouldBeAllowed()) { return http.requestAuthentication(DIGEST_AUTH); } if (FS_LIB.exists(csv_writer::filename)) {
fs::File csv_file = FS_LIB.open(csv_writer::filename, "r"); http.sendHeader("Content-Length", String(csv_file.size())); http.streamFile(csv_file, F("text/csv")); csv_file.close(); } else { http.send(204, F("text/html"), F("No data available.")); } } void handleDeleteCSV() { if (!shouldBeAllowed()) { return http.requestAuthentication(DIGEST_AUTH); } Serial.print("Removing CSV file..."); FS_LIB.remove(csv_writer::filename); Serial.println(" Done!"); http.sendHeader("Location", "/"); http.send(303); } void handlePageNotFound() { http.send(404, F("text/plain"), F("404: Not found")); } }