From f94b3115296bacf360a476858d726b7e107a9b14 Mon Sep 17 00:00:00 2001 From: Eric Duminil Date: Sat, 15 May 2021 21:51:45 +0200 Subject: [PATCH] Tryint to load webconf first. Badly broken --- ampel-firmware/ampel-firmware.h | 3 + ampel-firmware/ampel-firmware.ino | 13 +- ampel-firmware/web_config.cpp | 427 ++++++++++++++++++++++++++++++ ampel-firmware/web_config.h | 22 ++ 4 files changed, 456 insertions(+), 9 deletions(-) create mode 100644 ampel-firmware/web_config.cpp create mode 100644 ampel-firmware/web_config.h diff --git a/ampel-firmware/ampel-firmware.h b/ampel-firmware/ampel-firmware.h index 49c4901..97fabc1 100644 --- a/ampel-firmware/ampel-firmware.h +++ b/ampel-firmware/ampel-firmware.h @@ -8,6 +8,9 @@ # error Missing config.h file. Please copy config.public.h to config.h. #endif +// Needed for offline config too. +#include "web_config.h" + #ifdef AMPEL_CSV # include "csv_writer.h" #endif diff --git a/ampel-firmware/ampel-firmware.ino b/ampel-firmware/ampel-firmware.ino index ca1ab56..da62288 100644 --- a/ampel-firmware/ampel-firmware.ino +++ b/ampel-firmware/ampel-firmware.ino @@ -65,6 +65,8 @@ void setup() { Serial.begin(BAUDS); + web_config::initialize(); + pinMode(0, INPUT); // Flash button (used for forced calibration) Serial.println(); @@ -88,7 +90,7 @@ void setup() { #ifdef AMPEL_WIFI // Structure doesn't make sense anymore # ifdef AMPEL_HTTP //TODO: Rename. Not just web_server - web_server::initialize(); + // web_server::initialize(); # endif #endif @@ -184,15 +186,8 @@ void checkFlashButton() { void keepServicesAlive() { #ifdef AMPEL_WIFI -# ifdef AMPEL_HTTP - web_server::update(); //FIXME: Weird structure -# endif + web_config::update(); if (WiFi.status() == WL_CONNECTED) { -# if defined(ESP8266) - //NOTE: Sadly, there seems to be a bug in the current MDNS implementation. - // It stops working after 2 minutes. And forcing a restart leads to a memory leak. - MDNS.update(); -# endif ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s. # ifdef AMPEL_MQTT mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s. diff --git a/ampel-firmware/web_config.cpp b/ampel-firmware/web_config.cpp new file mode 100644 index 0000000..f65a46c --- /dev/null +++ b/ampel-firmware/web_config.cpp @@ -0,0 +1,427 @@ +#include "web_config.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_config { + + 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 + + DNSServer dnsServer; + + IotWebConf *iotWebConf; + +#define STRING_LEN 64 + +// -- Configuration specific key. The value should be modified if config structure was changed. + const char config_version[] = "ampel_test_v3"; + + static const char chooserValues[][STRING_LEN] = { "red", "blue", "darkYellow" }; + static const char chooserNames[][STRING_LEN] = { "Red", "Blue", "Dark yellow" }; + + iotwebconf::TextTParameter stringParam = iotwebconf::Builder>( + "stringParam").label("String param").build(); + iotwebconf::ParameterGroup group1 = iotwebconf::ParameterGroup("group1", ""); + iotwebconf::IntTParameter intParam = + iotwebconf::Builder>("intParam").label("Int param").defaultValue(30).min(1).max( + 100).step(1).placeholder("1..100").build(); +// -- We can add a legend to the separator + iotwebconf::ParameterGroup group2 = iotwebconf::ParameterGroup("c_factor", "Calibration factor"); + iotwebconf::FloatTParameter floatParam = iotwebconf::Builder("floatParam").label( + "Float param").defaultValue(0.0).step(0.1).placeholder("e.g. 23.4").build(); + iotwebconf::CheckboxTParameter checkboxParam = + iotwebconf::Builder("checkParam").label("Check param").defaultValue(true).build(); + iotwebconf::SelectTParameter chooserParam = + iotwebconf::Builder>("chooseParam").label("Choose param").optionValues( + (const char*) chooserValues).optionNames((const char*) chooserNames).optionCount( + sizeof(chooserValues) / STRING_LEN).nameLength(STRING_LEN).build(); + + void update() { + iotWebConf->doLoop(); // Listen for HTTP requests from clients + } + + void initialize() { + iotWebConf = new IotWebConf(ampel.sensorId, &dnsServer, &http, HTTP_PASSWORD, config_version); + + const int ONBOARD_LED_PIN = 2; +# ifdef ESP8266 + iotWebConf->setStatusPin(ONBOARD_LED_PIN, LOW); +# else + iotWebConf->setStatusPin(ONBOARD_LED_PIN, HIGH); +# endif + iotWebConf->setWifiConnectionTimeoutMs(1000UL * WIFI_TIMEOUT); + +#if defined(ESP8266) + WiFi.hostname(ampel.sensorId); +#elif defined(ESP32) + WiFi.setHostname(ampel.sensorId); +#endif + + group1.addItem(&intParam); + group2.addItem(&floatParam); + group2.addItem(&checkboxParam); + group2.addItem(&chooserParam); + + iotWebConf->addSystemParameter(&stringParam); + iotWebConf->addParameterGroup(&group1); + iotWebConf->addParameterGroup(&group2); + + iotWebConf->setWifiConnectionCallback([]() { + led_effects::showKITTWheel(color::green); + Serial.println(); + Serial.print(F("WiFi - Connected! IP address: ")); + IPAddress address = WiFi.localIP(); + snprintf(wifi::local_ip, sizeof(wifi::local_ip), "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); + + ntp::initialize(); + + //FIXME: Somehow already started +// if (MDNS.begin(ampel.sensorId)) { // Start the mDNS responder for SENSOR_ID.local +// MDNS.addService("http", "tcp", 80); +// Serial.println(F("mDNS responder started")); +// } else { +// Serial.println(F("Error setting up MDNS responder!")); +// } + +# ifdef AMPEL_MQTT + mqtt::initialize(ampel.sensorId); +# endif + + Serial.println(wifi::local_ip); + 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()); + }); + + iotWebConf->setWifiConnectionFailedHandler([]() -> iotwebconf::WifiAuthInfo* { + led_effects::showKITTWheel(color::red); + Serial.println(F("Connection to WiFi failed")); + return NULL; + }); + + iotWebConf->skipApStartup(); + //TODO: Add callbacks + //TODO: Add LED effects + //TODO: Allow offline config loading + //TODO: Add default values for SSID/password + //TODO: Add other params + //TODO: Use HTTP_USER / HTTP_PASSWORD for config + //TODO: Move to own class + //TODO: Remove AP Password config? + //TODO: Save LoRaWAN key if possible? + //FIXME: Why does MQTT fail? (on ESP32) + +// iotWebConf->loadConfig(); + Serial.println("<<<<<<<<<<<<<<<"); + Serial.println(stringParam.value()); + Serial.println(intParam.value()); + Serial.println(floatParam.value()); + iotWebConf->init(); + Serial.println(stringParam.value()); + Serial.println(intParam.value()); + Serial.println(floatParam.value()); + Serial.println(">>>>>>>>>>>>>>>"); + + sensor_console::defineCommand("reset_config", []() { + Serial.println(F("Resetting config...")); + iotWebConf->getSystemParameterGroup()->applyDefaultValue(); + iotWebConf->saveConfig(); + Serial.println(F("Done!")); + }, F("(resets the complete IotWeb config)")); + + 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" + "
%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
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.on("/config", [] { + iotWebConf->handleConfig(); + }); + http.onNotFound([]() { + iotWebConf->handleNotFound(); + }); + + //TODO: Only once wifi connected + } + + // 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 (iotWebConf->handleCaptivePortal()) { + // -- Captive portal requests were already served. + return; + } + 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 : 1812 - 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, 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")); + } +} diff --git a/ampel-firmware/web_config.h b/ampel-firmware/web_config.h new file mode 100644 index 0000000..3e21bce --- /dev/null +++ b/ampel-firmware/web_config.h @@ -0,0 +1,22 @@ +#ifndef AMPEL_WEB_CONFIG_H_ +#define AMPEL_WEB_CONFIG_H_ + +#if defined(ESP8266) +# include +#elif defined(ESP32) +# include +#endif + +//#include "config.h" + +#include +#include + +//#include "util.h" +//#include "sensor_console.h" + +namespace web_config { + void initialize(); + void update(); +} +#endif -- GitLab