#include "web_server.h" #include "web_config.h" #include "util.h" #include "ntp.h" #include "wifi_util.h" #include "co2_sensor.h" #include "sensor_console.h" #include "csv_writer.h" #include "mqtt.h" #include "lorawan.h" #if defined(ESP8266) # include #elif defined(ESP32) # include #endif namespace web_server { const char *header_template; const char *body1_template; const char *body2_template; const char *script_template; void handleWebServerRoot(); void handlePageNotFound(); void handleWebServerCommand(); void handleDeleteCSV(); void handleWebServerCSV(); const __FlashStringHelper* showHTMLIf(bool is_active) { return is_active ? F("") : F("hidden"); } const __FlashStringHelper* yesOrNo(bool is_active) { return is_active ? F("Yes") : F("No"); } void definePages() { header_template = PSTR("" "" "%d ppm - CO2 SENSOR - %s - %s" "" // HfT Favicon "" // Responsive grid: "" "" // JS Graphs: "" // Fullscreen "" // Refresh after every measurement. // "" "" "" "

HfT-Stuttgart CO2 Ampel

" "
" "" "
" "
"// Graph placeholder "
" "
" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" #if defined(ESP32) "" "" "" "" "" "" "" #endif ); body2_template = PSTR( "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
%s
CO2%5d ppm
Temperature%.1f℃
Humidity%.1f%%
Last measurement%s
Timestep%5d s
CSV
Last write%s
Interval%5d s
Available space%d kB
MQTT
Connected?%s
Last publish%s
Interval%5d s
LoRaWAN
Connected?%s
Frequency%s MHz
Last transmission%s
Interval%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
Frag%3d%%
Max loop duration%5d ms
Board%s
ID%s
Ampel firmware%s
Uptime%2d d %4d h %02d min %02d s
" "
" "
" "
" "" "
" "" // Can be useful in AP mode "
" "Source code " "Documentation" "" "" ""); // Web-server web_config::http.on("/", handleWebServerRoot); web_config::http.on("/command", handleWebServerCommand); web_config::http.on(csv_writer::filename, handleWebServerCSV); web_config::http.on("/delete_csv", HTTP_POST, handleDeleteCSV); } /* * Allow access if Ampel is in access point mode, * if http_user or http_password are empty, * or if provided credentials match */ bool shouldBeAllowed() { return wifi::isAccessPoint() || strcmp(config::http_user, "") == 0 || strcmp(config::ampel_password(), "") == 0 || web_config::http.authenticate(config::http_user, config::ampel_password()); } void handleWebServerRoot() { 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. Higher than 2000 apparently crashes the ESP8266 char content[1600]; // Current size (with Lorawan, timesteps and long thing name): // INFO - Header size : 1347 - Body1 size : 1448 - Body2 size : 1475 - Script size : 1507 snprintf_P(content, sizeof(content), header_template, sensor::co2, config::ampel_name(), wifi::local_ip); Serial.print(F("INFO - Header size : ")); Serial.print(strlen(content)); web_config::http.setContentLength(CONTENT_LENGTH_UNKNOWN); web_config::http.send_P(200, PSTR("text/html"), content); // Body snprintf_P(content, sizeof(content), body1_template, csv_writer::filename, config::ampel_name(), sensor::co2, sensor::temperature, sensor::humidity, sensor::timestamp, config::measurement_timestep, showHTMLIf(config::is_csv_active()), csv_writer::last_successful_write, config::csv_interval, csv_writer::getAvailableSpace() / 1024, showHTMLIf(config::is_mqtt_active()), yesOrNo(mqtt::connected), mqtt::last_successful_publish, config::mqtt_sending_interval #if defined(ESP32) , showHTMLIf(config::is_lorawan_active()), yesOrNo(lorawan::connected), config::lorawan_frequency_plan, lorawan::last_transmission, config::lorawan_sending_interval #endif ); Serial.print(F(" - Body1 size : ")); Serial.print(strlen(content)); web_config::http.sendContent(content); snprintf_P(content, sizeof(content), body2_template, sensor::getTemperatureOffset(), yesOrNo(config::auto_calibrate_sensor), config::ampel_name(), config::ampel_name(), wifi::local_ip, wifi::local_ip, ampel.macAddress, ESP.getFreeHeap(), esp_get_max_free_block_size(), esp_get_heap_fragmentation(), ampel.max_loop_duration, ampel.board, ampel.sensorId, ampel.version, dd, hh, mm, ss, showHTMLIf(!ntp::connected_at_least_once)); Serial.print(F(" - Body2 size : ")); Serial.print(strlen(content)); web_config::http.sendContent(content); // Script snprintf_P(content, sizeof(content), script_template, csv_writer::filename, config::ampel_name()); Serial.print(F(" - Script size : ")); Serial.println(strlen(content)); web_config::http.sendContent(content); } void handleWebServerCSV() { 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()); web_config::http.sendHeader("Content-Length", csv_size); web_config::http.streamFile(csv_file, F("text/csv")); csv_file.close(); } else { web_config::http.send(204, F("text/html"), F("No data available.")); } } void handleDeleteCSV() { if (!shouldBeAllowed()) { return web_config::http.requestAuthentication(DIGEST_AUTH); } Serial.print(F("Removing CSV file...")); FS_LIB.remove(csv_writer::filename); Serial.println(F(" Done!")); web_config::http.sendHeader("Location", "/"); web_config::http.send(303); } void handleWebServerCommand() { if (!shouldBeAllowed()) { return web_config::http.requestAuthentication(DIGEST_AUTH); } web_config::http.sendHeader("Location", "/"); web_config::http.send(303); sensor_console::execute(web_config::http.arg("send").c_str()); } void handlePageNotFound() { web_config::http.send(404, F("text/plain"), F("404: Not found")); } }