#include "web_server.h" 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 handlePageNotFound(); void handleWebServerCommand(); #ifdef AMPEL_CSV void handleDeleteCSV(); void handleWebServerCSV(); #endif #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("" "\n" "%d ppm - CO2 SENSOR - %s - %s\n" "\n" // HfT Favicon "\n" // Responsive grid: "\n" "\n" // JS Graphs: "\n" // Fullscreen "\n" // Refresh after every measurement. // "\n" "\n" "\n" "

HfT-Stuttgart CO2 Ampel

\n" "
\n" "\n" "
\n" "
\n"// Graph placeholder "
\n" "
\n" "\n"); body_template = PSTR("\n" "\n" "\n" "\n" "\n" "\n" #ifdef AMPEL_CSV "\n" "\n" "\n" "\n" #endif #ifdef AMPEL_MQTT "\n" "\n" "\n" "\n" #endif #if defined(AMPEL_LORAWAN) && defined(ESP32) "\n" "\n" "\n" "\n" "\n" #endif "\n" "\n" //TODO: Read it from sensor? "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "
%s
CO2 concentration%5d ppm
Temperature%.1f℃
Humidity%.1f%%
Last measurement%s
Measurement timestep%5d s
CSV
Last write%s
Timestep%5d s
Available drive space%d kB
MQTT
Connected?%s
Last publish%s
Timestep%5d s
LoRaWAN
Connected?%s
Frequency%s MHz
Last transmission%s
Timestep%5d s
Sensor
Temperature offset%.1fK
Auto-calibration?%s
Local address%s.local
Local IP%s
MAC%s
Free heap space%6d bytes
Largest heap block%6d bytes
Max loop duration%5d ms
Board%s
Ampel firmware%s
Uptime%2d d %4d h %02d min %02d s
\n" "
\n" "
\n" #ifdef AMPEL_CSV "
" "" "
\n" #endif "
\n"); script_template = PSTR( "Source code\n" "Documentation\n" #ifdef AMPEL_CSV "\n" #endif "\n" ""); // Web-server http.on("/", handleWebServerRoot); http.on("/command", handleWebServerCommand); #ifdef AMPEL_CSV http.on(csv_writer::filename, handleWebServerCSV); //NOTE: csv_writer should have been initialized first. http.on("/delete_csv", HTTP_POST, handleDeleteCSV); #endif http.onNotFound(handlePageNotFound); http.begin(); Serial.print(F("You can access this sensor via http://")); Serial.print(ampel.sensorId); 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(); uint8_t dd = ss / 86400; ss -= dd * 86400; unsigned int hh = ss / 3600; ss -= hh * 3600; uint8_t mm = ss / 60; ss -= mm * 60; //NOTE: Splitting in multiple parts in order to use less RAM char content[2000]; // Update if needed // INFO - Header size : 1767 - Body size : 1991 - Script size : 1909 snprintf_P(content, sizeof(content), header_template, sensor::co2, ampel.sensorId, wifi::local_ip #ifdef AMPEL_CSV , csv_writer::filename #endif ); // Serial.print(F("INFO - Header size : ")); // Serial.print(strlen(content)); http.setContentLength(CONTENT_LENGTH_UNKNOWN); http.send_P(200, PSTR("text/html"), content); // Body snprintf_P(content, sizeof(content), body_template, ampel.sensorId, sensor::co2, sensor::temperature, sensor::humidity, sensor::timestamp, config::measurement_timestep, #ifdef AMPEL_CSV csv_writer::last_successful_write, config::csv_interval, csv_writer::getAvailableSpace() / 1024, #endif #ifdef AMPEL_MQTT mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish, config::mqtt_sending_interval, #endif #if defined(AMPEL_LORAWAN) && defined(ESP32) lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission, config::lorawan_sending_interval, #endif config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId, ampel.sensorId, wifi::local_ip, wifi::local_ip, ampel.macAddress, ESP.getFreeHeap(), esp_get_max_free_block_size(), ampel.max_loop_duration, ampel.board, ampel.version, dd, hh, mm, ss); // Serial.print(F(" - Body size : ")); // Serial.print(strlen(content)); http.sendContent(content); // Script snprintf_P(content, sizeof(content), script_template #ifdef AMPEL_CSV , csv_writer::filename, ampel.sensorId #endif ); // Serial.print(F(" - Script size : ")); // Serial.println(strlen(content)); http.sendContent(content); } #ifdef AMPEL_CSV 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"); char csv_size[10]; snprintf(csv_size, sizeof(csv_size), "%d", csv_file.size()); http.sendHeader("Content-Length", csv_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(F("Removing CSV file...")); FS_LIB.remove(csv_writer::filename); Serial.println(F(" Done!")); http.sendHeader("Location", "/"); http.send(303); } #endif void handleWebServerCommand() { if (!shouldBeAllowed()) { return http.requestAuthentication(DIGEST_AUTH); } http.sendHeader("Location", "/"); http.send(303); sensor_console::execute(http.arg("send").c_str()); } void handlePageNotFound() { http.send(404, F("text/plain"), F("404: Not found")); } }