Commit f94b3115 authored by Eric Duminil's avatar Eric Duminil
Browse files

Tryint to load webconf first. Badly broken

parent c9eaca3f
......@@ -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
......
......@@ -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.
......
#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<STRING_LEN> stringParam = iotwebconf::Builder<iotwebconf::TextTParameter< STRING_LEN>>(
"stringParam").label("String param").build();
iotwebconf::ParameterGroup group1 = iotwebconf::ParameterGroup("group1", "");
iotwebconf::IntTParameter<int16_t> intParam =
iotwebconf::Builder<iotwebconf::IntTParameter<int16_t>>("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<iotwebconf::FloatTParameter>("floatParam").label(
"Float param").defaultValue(0.0).step(0.1).placeholder("e.g. 23.4").build();
iotwebconf::CheckboxTParameter checkboxParam =
iotwebconf::Builder<iotwebconf::CheckboxTParameter>("checkParam").label("Check param").defaultValue(true).build();
iotwebconf::SelectTParameter<STRING_LEN> chooserParam =
iotwebconf::Builder<iotwebconf::SelectTParameter< STRING_LEN>>("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("<!doctype html><html lang=en>"
"<head>\n"
"<title>%d ppm - CO2 SENSOR - %s - %s</title>\n"
"<meta charset='UTF-8'>\n"
// HfT Favicon
"<link rel='icon' type='image/png' sizes='16x16' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAHtJREFUOE9jvMnA+5+BAsBIFQMkl85h+P3kGcOb8jqwW+TPH2H4de0GA29UGNxtfx49YWCRk0HwHz5iuKegwwB2AS4DkA2F6VR6cAWsEQbgBqDY9vARw/ejJ+Au+LxsFcPz6BSwHpwGYPMCSS6gyAAKYhESiKMGjPgwAADopHVhn5ynEwAAAABJRU5ErkJggg=='/>\n"
// 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> Ampel</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='/config' class='pure-menu-link'>Config</a></li>\n"
"<li class='pure-menu-item'><a href='#table' class='pure-menu-link'>Info</a></li>\n"
#ifdef AMPEL_CSV
"<li class='pure-menu-item'><a href='#graph' class='pure-menu-link'>Graph</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"
#endif
"<li class='pure-menu-item' id='led'>&#11044;</li>\n" // LED
"</ul></div></div>\n"
"<script>\n"
// Show a colored dot on the webpage, with a similar color than on LED Ring.
"hue=(1-(Math.min(Math.max(parseInt(document.title),500),1600)-500)/1100)*120;\n"
"document.getElementById('led').style.color=['hsl(',hue,',100%%,50%%)'].join('');\n"
"</script>\n"
"<div class='pure-g'>\n"
"<div class='pure-u-1' id='graph'></div>\n"// Graph placeholder
"</div>\n"
"<div class='pure-g'>\n"
"<table id='table' class='pure-table-striped pure-u-1 pure-u-md-1-2'>\n");
body_template =
PSTR("<tr><th colspan='2'>%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"
#ifdef AMPEL_CSV
"<tr><th colspan='2'>CSV</th></tr>\n"
"<tr><td>Last write</td><td>%s</td></tr>\n"
"<tr><td>Timestep</td><td>%5d s</td></tr>\n"
"<tr><td>Available drive space</td><td>%d kB</td></tr>\n"
#endif
#ifdef AMPEL_MQTT
"<tr><th colspan='2'>MQTT</th></tr>\n"
"<tr><td>Connected?</td><td>%s</td></tr>\n"
"<tr><td>Last publish</td><td>%s</td></tr>\n"
"<tr><td>Timestep</td><td>%5d s</td></tr>\n"
#endif
#if defined(AMPEL_LORAWAN) && defined(ESP32)
"<tr><th colspan='2'>LoRaWAN</th></tr>\n"
"<tr><td>Connected?</td><td>%s</td></tr>\n"
"<tr><td>Frequency</td><td>%s MHz</td></tr>\n"
"<tr><td>Last transmission</td><td>%s</td></tr>\n"
"<tr><td>Timestep</td><td>%5d s</td></tr>\n"
#endif
"<tr><th colspan='2'>Sensor</th></tr>\n"
"<tr><td>Temperature offset</td><td>%.1fK</td></tr>\n" //TODO: Read it from sensor?
"<tr><td>Auto-calibration?</td><td>%s</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>Largest heap block</td><td>%6d bytes</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>Ampel firmware</td><td>%s</td></tr>\n"
"<tr><td>Uptime</td><td>%2d d %4d h %02d min %02d s</td></tr>\n"
"</table>\n"
"<div id='log' class='pure-u-1 pure-u-md-1-2'></div>\n"
"<form action='/command'><input type='text' id='send' name='send'><input type='submit' value='Send'></form>\n"
#ifdef AMPEL_CSV
"<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"
#endif
"</div>\n");
script_template =
PSTR(
"<a href='https://transfer.hft-stuttgart.de/gitlab/co2ampel/ampel-firmware' target='_blank'>Source code</a>\n"
"<a href='https://transfer.hft-stuttgart.de/gitlab/co2ampel/ampel-documentation' target='_blank'>Documentation</a>\n"
#ifdef AMPEL_CSV
"<script>\n"
"document.body.style.cursor = 'default';\n"
"fetch('%s',{credentials:'include'})\n"
".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"
#endif
"</body>\n"
"</html>");
// 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"));
}
}
#ifndef AMPEL_WEB_CONFIG_H_
#define AMPEL_WEB_CONFIG_H_
#if defined(ESP8266)
# include <ESP8266WebServer.h>
#elif defined(ESP32)
# include <WebServer.h>
#endif
//#include "config.h"
#include <IotWebConf.h>
#include <IotWebConfTParameter.h>
//#include "util.h"
//#include "sensor_console.h"
namespace web_config {
void initialize();
void update();
}
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment