#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"
"\n"
"\n"
"\n"
"
\n"// Graph placeholder
"
\n"
"\n"
"
\n");
body_template =
PSTR("%s |
\n"
"CO2 concentration | %5d ppm |
\n"
"Temperature | %.1f℃ |
\n"
"Humidity | %.1f%% |
\n"
"Last measurement | %s |
\n"
"Measurement timestep | %5d s |
\n"
#ifdef AMPEL_CSV
"CSV |
\n"
"Last write | %s |
\n"
"Timestep | %5d s |
\n"
"Available drive space | %d kB |
\n"
#endif
#ifdef AMPEL_MQTT
"MQTT |
\n"
"Connected? | %s |
\n"
"Last publish | %s |
\n"
"Timestep | %5d s |
\n"
#endif
#if defined(AMPEL_LORAWAN) && defined(ESP32)
"LoRaWAN |
\n"
"Connected? | %s |
\n"
"Frequency | %s MHz |
\n"
"Last transmission | %s |
\n"
"Timestep | %5d s |
\n"
#endif
"Sensor |
\n"
"Temperature offset | %.1fK |
\n" //TODO: Read it from sensor?
"Auto-calibration? | %s |
\n"
"Local address | %s.local |
\n"
"Local IP | %s |
\n"
"MAC | %s |
\n"
"Free heap space | %6d bytes |
\n"
"Largest heap block | %6d bytes |
\n"
"Max loop duration | %5d ms |
\n"
"Board | %s |
\n"
"Ampel firmware | %s |
\n"
"Uptime | %2d d %4d h %02d min %02d s |
\n"
"
\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"));
}
}