Commit 6db31581 authored by Eric Duminil's avatar Eric Duminil
Browse files

Removed everything. S8 not recognized

parent 2fe7a4e7
Pipeline #6018 failed with stage
in 21 seconds
#ifndef AMPEL_WEB_CONFIG_H_
#define AMPEL_WEB_CONFIG_H_
#if defined(ESP8266)
# include <ESP8266WebServer.h>
#elif defined(ESP32)
# include <WebServer.h>
#endif
namespace config {
void save(); // Save config to EEPROM
char* ampel_name();
// WiFi
char* selected_ssid();
char* ampel_password(); // For Access Point, and for HTML page
extern bool &is_wifi_on; // [true / false]
extern uint16_t &wifi_timeout; // [s]
// Sensor
extern uint16_t &measurement_timestep; // [s] Value between 2 and 1800 (range for SCD30 sensor).
extern uint16_t &altitude_above_sea_level; // [m]
extern uint16_t &co2_calibration_level; // [ppm]
extern bool &auto_calibrate_sensor; // [true / false]
extern float &temperature_offset; // [K] Sign isn't relevant.
// LED
extern uint8_t &max_brightness;
extern uint8_t &min_brightness;
extern uint16_t &led_count;
// Time server
extern char *ntp_server;
extern int16_t &time_zone; // [h]
extern bool &daylight_saving_time; // [true / false]
//CSV
bool is_csv_active(); // [true / false]
extern uint16_t &csv_interval; // [s]
// MQTT
bool is_mqtt_active(); // [true / false]
extern char *mqtt_server;
extern char *mqtt_user;
extern char *mqtt_password;
extern char *mqtt_topic_prefix;
extern uint16_t &mqtt_port;
extern uint16_t &mqtt_sending_interval; // [s]
extern bool &mqtt_encryption; // [true / false]
extern bool &allow_mqtt_commands; // [true / false]
// HTTP
const char http_user[] = "admin"; // "admin" by default
// LORAWAN
#if defined(ESP32)
bool is_lorawan_active(); // also defined for ESP8266, and set to false
extern uint16_t &lorawan_sending_interval;
extern char *lorawan_device_eui;
extern char *lorawan_app_key;
extern char *lorawan_app_eui;
#endif
// Transmission rate
constexpr uint32_t bauds = 115200;
}
namespace web_config {
void initialize();
void setWifiConnectionCallback(void (*success_function)());
void setWifiFailCallback(void (*fail_function)());
void setWifiConnectingCallback(void (*connecting_function)());
void setApModeCallback(void (*ap_mode_function)());
void update();
#if defined(ESP8266)
extern ESP8266WebServer http;
#elif defined(ESP32)
extern WebServer http;
#endif
}
#endif
#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 <ESP8266WebServer.h>
#elif defined(ESP32)
# include <WebServer.h>
#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("<!doctype html><html lang=en>"
"<head>"
"<title>%d ppm - CO2 SENSOR - %s - %s</title>"
"<meta charset='UTF-8'>"
// HfT Favicon
"<link rel='icon' type='image/png' sizes='16x16' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAHtJREFUOE9jvMnA+5+BAsBIFQMkl85h+P3kGcOb8jqwW+TPH2H4de0GA29UGNxtfx49YWCRk0HwHz5iuKegwwB2AS4DkA2F6VR6cAWsEQbgBqDY9vARw/ejJ+Au+LxsFcPz6BSwHpwGYPMCSS6gyAAKYhESiKMGjPgwAADopHVhn5ynEwAAAABJRU5ErkJggg=='/>"
// Responsive grid:
"<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.6/build/pure-min.css'>"
"<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.6/build/grids-responsive-min.css'>"
// JS Graphs:
"<script src='https://cdn.plot.ly/plotly-basic-2.9.0.min.js'></script>"
// Fullscreen
"<meta name='viewport' content='width=device-width, initial-scale=1'>"
// Refresh after every measurement.
// "<meta http-equiv='refresh' content='%d'>"
"</head>"
"<body>"
"<div class='pure-g'><div class='pure-u-1'><div class='pure-menu'><h2 class='pure-menu-heading'>HfT-Stuttgart CO<sub>2</sub> Ampel</h2></div></div>"
"<div class='pure-u-1'><ul class='pure-menu pure-menu-horizontal pure-menu-list'>"
"<li class='pure-menu-item'><a href='/config' class='pure-menu-link'>Config</a></li>"
"<li class='pure-menu-item'><a href='#table' class='pure-menu-link'>Info</a></li>"
"<li class='pure-menu-item'><a href='#graph' class='pure-menu-link'>Graph</a></li>"
"<li class='pure-menu-item'><a href='#log' class='pure-menu-link'>Log</a></li>");
body1_template =
PSTR(
"<li class='pure-menu-item'><a href='%s' class='pure-menu-link'>Download CSV</a></li>" "<li class='pure-menu-item' id='led'>&#11044;</li>" // LED
"</ul></div></div>" "<script>"
// 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;" "document.getElementById('led').style.color=['hsl(',hue,',100%%,50%%)'].join('');" "</script>" "<div class='pure-g'>" "<div class='pure-u-1' id='graph'></div>"// Graph placeholder
"</div>" "<div class='pure-g'>" "<table id='table' class='pure-table-striped pure-u-1 pure-u-md-1-2'>" "<tr><th colspan='2'>%s</th></tr>" "<tr><td>CO<sub>2</sub></td><td>%5d ppm</td></tr>" "<tr><td>Temperature</td><td>%.1f&#8451;</td></tr>" "<tr><td>Humidity</td><td>%.1f%%</td></tr>" "<tr><td>Last measurement</td><td>%s</td></tr>" "<tr><td>Timestep</td><td>%5d s</td></tr>" "<tbody %s>" "<tr><th colspan='2'>CSV</th></tr>" "<tr><td>Last write</td><td>%s</td></tr>" "<tr><td>Interval</td><td>%5d s</td></tr>" "<tr><td>Available space</td><td>%d kB</td></tr>" "</tbody>" "<tbody %s>" "<tr><th colspan='2'>MQTT</th></tr>" "<tr><td>Connected?</td><td>%s</td></tr>" "<tr><td>Last publish</td><td>%s</td></tr>" "<tr><td>Interval</td><td>%5d s</td></tr>" "</tbody>"
#if defined(ESP32)
"<tbody %s>"
"<tr><th colspan='2'>LoRaWAN</th></tr>"
"<tr><td>Connected?</td><td>%s</td></tr>"
"<tr><td>Frequency</td><td>%s MHz</td></tr>"
"<tr><td>Last transmission</td><td>%s</td></tr>"
"<tr><td>Interval</td><td>%5d s</td></tr>"
"</tbody>"
#endif
);
body2_template =
PSTR(
"<tr><th colspan='2'>Sensor</th></tr>"
"<tr><td>Temperature offset</td><td>%.1fK</td></tr>"
"<tr><td>Auto-calibration?</td><td>%s</td></tr>"
"<tr><td>Local address</td><td><a href='http://%s.local/'>%s.local</a></td></tr>"
"<tr><td>Local IP</td><td><a href='http://%s'>%s</a></td></tr>"
"<tr><td>MAC</td><td>%s</td></tr>"
"<tr><td>Free heap space</td><td>%6d bytes</td></tr>"
"<tr><td>Largest heap block</td><td>%6d bytes</td></tr>"
"<tr><td>Frag</td><td>%3d%%</td></tr>"
"<tr><td>Max loop duration</td><td>%5d ms</td></tr>"
"<tr><td>Board</td><td>%s</td></tr>"
"<tr><td>ID</td><td>%s</td></tr>"
"<tr><td>Ampel firmware</td><td>%s</td></tr>"
"<tr><td>Uptime</td><td>%2d d %4d h %02d min %02d s</td></tr>"
"</table>"
"<div id='log' class='pure-u-1 pure-u-md-1-2'></div>"
"<form action='/command'><input type='text' id='send' name='send'><input type='submit' value='Send'></form>"
"<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>"
"<button onclick=\"fetch('/command?send=set_time '+Math.floor(Date.now()/1000))\" %s>Set time!</button>" // Can be useful in AP mode
"</div>"
"<a href='https://transfer.hft-stuttgart.de/gitlab/co2ampel/ampel-firmware' target='_blank'>Source code</a>&nbsp;"
"<a href='https://transfer.hft-stuttgart.de/gitlab/co2ampel/ampel-documentation' target='_blank'>Documentation</a>"
"<script>"
"document.body.style.cursor='default';");
script_template = PSTR("fetch('%s',{credentials:'include'})"
".then(r=>r.text())"
".then(c2t)"
".then(t=>document.getElementById('log').appendChild(t))"
".then(_=>Plotly.newPlot('graph',data,layout,{displaylogo:false}))"
".catch(console.error);"
"xs=[];y1=[];y2=[];y3=[];"
"d={x:xs,type:'scatter',mode:'lines+markers',marker:{size:3}};" // circle marker (symbol 0), from https://plotly.com/python/marker-style/
"data=["
"{...d,...{y:y1,name:'CO<sub>2</sub>',line:{color:'#2ca02c'}}},"
"{...d,...{y:y2,name:'Temperature',yaxis:'y2',line:{color:'#ff7f0e',dash:'dot'}}},"
"{...d,...{y:y3,name:'Humidity',yaxis:'y3',line:{color:'#1f77b4',dash:'dot'}}}];"
"layout={height:600,title:'%s',legend:{xanchor:'right',x:0.2,y:1.0},"
"xaxis:{domain:[0.0,0.85]},yaxis:{ticksuffix:'ppm',range:[0,2000],dtick:200},"
"yaxis2:{overlaying:'y',side:'right',ticksuffix:'°C',position:0.9,anchor:'free',range:[0,30],dtick:3},"
"yaxis3:{overlaying:'y',side:'right',ticksuffix:'%%',position:0.95,anchor:'free',range:[0,100],dtick:10}"
"};"
"function c2t(t){"
"t=t.trim();"
"ls=t.split('\\n');"
"tb=document.createElement('table');"
"tb.className='pure-table-striped';"
"n=ls.length;"
"ld=NaN;"
"ls.forEach((l,i)=>{"
"fs=l.split(';');"
//Don't display points without time
"if(fs[0].includes('1970-')){return};"
"d=Date.parse(fs[0]);"
//Split curves when points are more than 1h apart
"if(d-ld>36e5){"
"xs.push(NaN);"
"y1.push(NaN);"
"y2.push(NaN);"
"y3.push(NaN);"
"}"
"ld=d;"
"xs.push(fs[0]);"
"y1.push(fs[1]);"
"y2.push(fs[2]);"
"y3.push(fs[3]);"
"if(i>4&&i<n-12){if(i==5){fs=['...','...','...','...']}else{return;}}"
"r=document.createElement('tr');"
"fs.forEach((f,_)=>{"
"c=document.createElement(i<2?'th':'td');"
"c.appendChild(document.createTextNode(f));"
"r.appendChild(c);});"
"tb.appendChild(r);});"
"return tb;}"
"</script>"
"</body>"
"</html>");
// 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"));
}
}
#ifndef WEB_SERVER_H_
#define WEB_SERVER_H_
namespace web_server {
void definePages();
}
#endif
#include "wifi_util.h"
#include "web_config.h"
#include "util.h"
#include "ntp.h"
#include "led_effects.h"
#include "sensor_console.h"
#if defined(ESP8266)
# include <ESP8266WiFi.h>
#elif defined(ESP32)
# include <WiFi.h>
#endif
namespace wifi {
char local_ip[16]; // "255.255.255.255\0"
bool connected() {
return WiFi.status() == WL_CONNECTED;
}
bool isAccessPoint() {
return WiFi.getMode() == WIFI_AP;
}
/*
* Connection attempt, called in blocking mode by setup(). This way, LED effects can be shown
* without needing callbacks, but only during wifi_timeout seconds.
* If connection fails, access point will be started indefinitely, and corresponding
* LED effects will be shown during 5 seconds.
*
* Afterwards, the non-blocking web_config::update() will be called inside loop, and the ampel
* can display CO2 levels.
*/
void tryConnection() {
for (int i = 0; i <= config::wifi_timeout + 5; i++) {
web_config::update();
sensor_console::checkSerialInput(); // To allow reset or ssid ... during startup
if (isAccessPoint()) {
led_effects::alert(color::turquoise);
} else if (connected()) {
break;
} else {
led_effects::showRainbowWheel();
}
Serial.print(".");
}
Serial.println();
}
void scanNetworks() {
Serial.println();
Serial.println(F("WiFi - Scanning..."));
bool async = false;
bool showHidden = true;
int n = WiFi.scanNetworks(async, showHidden);
for (int i = 0; i < n; ++i) {
Serial.print(F(" * '"));
Serial.print(WiFi.SSID(i));
Serial.print(F("' ("));
int16_t quality = 2 * (100 + WiFi.RSSI(i));
Serial.print(util::min(util::max(quality, 0), 100));
Serial.println(F(" %)"));
}
Serial.println(F("Done!"));
Serial.println();
}
void showLocalIp() {
Serial.print(F("WiFi - Local IP : "));
Serial.println(wifi::local_ip);
Serial.print(F("WiFi - SSID : "));
Serial.println(config::selected_ssid());
}
void defineCommands() {
sensor_console::defineCommand("wifi_scan", scanNetworks, F("(Scans available WiFi networks)"));
sensor_console::defineCommand("local_ip", showLocalIp, F("(Displays local IP and current SSID)"));
//TODO: Add "update!" command? https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266httpUpdate/examples/httpUpdate/httpUpdate.ino
}
}
#ifndef WIFI_UTIL_H_INCLUDED
#define WIFI_UTIL_H_INCLUDED
namespace wifi {
extern char local_ip[16];
void defineCommands();
bool connected();
bool isAccessPoint();
void tryConnection();
}
#endif
......@@ -26,9 +26,5 @@ board = ttgo-lora32-v1
framework = arduino
monitor_speed = 115200
lib_deps =
MCCI LoRaWAN LMIC library
S8_UART
build_flags =
-D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS
-D CFG_eu868=1
-D CFG_sx1276_radio=1
plerup/espsoftwareserial
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