Project 'ulrike.pado/ASYST' was moved to 'knight/ASYST'. Please update any links and bookmarks that may still have the old path.
Commit 983003b4 authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge branch 'develop'

Showing with 163 additions and 93 deletions
+163 -93
File moved
...@@ -2,16 +2,17 @@ ...@@ -2,16 +2,17 @@
#define AMPEL_UTIL_H_INCLUDED #define AMPEL_UTIL_H_INCLUDED
#include <Arduino.h> #include <Arduino.h>
#include "config.h" #include "config.h"
#include "wifi_util.h" // To get MAC
#include <WiFiUdp.h> //required for NTP #include <WiFiUdp.h> // required for NTP
#include "src/lib/NTPClient-master/NTPClient.h" // NTP #include "src/lib/NTPClient-master/NTPClient.h" // NTP
#if defined(ESP8266) #if defined(ESP8266)
# define BOARD "ESP8266" # define BOARD "ESP8266"
# include <ESP8266WiFi.h> // required to get MAC address
# define get_free_heap_size() system_get_free_heap_size() # define get_free_heap_size() system_get_free_heap_size()
#elif defined(ESP32) #elif defined(ESP32)
# define BOARD "ESP32" # define BOARD "ESP32"
# include <WiFi.h> // required to get MAC address
# define get_free_heap_size() esp_get_free_heap_size() # define get_free_heap_size() esp_get_free_heap_size()
#else #else
# define BOARD "Unknown" # define BOARD "Unknown"
...@@ -23,6 +24,18 @@ namespace ntp { ...@@ -23,6 +24,18 @@ namespace ntp {
String getLocalTime(); String getLocalTime();
} }
namespace util {
template<typename Tpa, typename Tpb>
inline auto min(const Tpa &a, const Tpb &b) -> decltype(a < b ? a : b) {
return b < a ? b : a;
}
template<typename Tpa, typename Tpb>
inline auto max(const Tpa &a, const Tpb &b) -> decltype(b > a ? b : a) {
return b > a ? b : a;
}
}
#define seconds() (millis() / 1000UL) #define seconds() (millis() / 1000UL)
extern uint32_t max_loop_duration; extern uint32_t max_loop_duration;
const extern String SENSOR_ID; const extern String SENSOR_ID;
......
...@@ -21,14 +21,16 @@ namespace web_server { ...@@ -21,14 +21,16 @@ namespace web_server {
const char *body_template; const char *body_template;
const char *script_template; const char *script_template;
void handleWebServerRoot(); void handleWebServerRoot();
void handleWebServerCSV();
void handlePageNotFound(); void handlePageNotFound();
#ifdef AMPEL_CSV
void handleDeleteCSV(); void handleDeleteCSV();
void handleWebServerCSV();
#endif
#if defined(ESP8266) #if defined(ESP8266)
ESP8266WebServer http(80); // Create a webserver object that listens for HTTP request on port 80 ESP8266WebServer http(80); // Create a webserver object that listens for HTTP request on port 80
#endif #elif defined(ESP32)
#if defined(ESP32)
WebServer http(80); WebServer http(80);
#endif #endif
...@@ -41,9 +43,9 @@ namespace web_server { ...@@ -41,9 +43,9 @@ namespace web_server {
PSTR("<!doctype html><html lang=en>" PSTR("<!doctype html><html lang=en>"
"<head>\n" "<head>\n"
"<title>%d ppm - CO2 SENSOR - %s - %s</title>\n" "<title>%d ppm - CO2 SENSOR - %s - %s</title>\n"
"<meta charset='UTF-8'>" "<meta charset='UTF-8'>\n"
// HfT Favicon // HfT Favicon
"<link rel='icon' type='image/png' sizes='16x16' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAHtJREFUOE9jvMnA+5+BAsBIFQMkl85h+P3kGcOb8jqwW+TPH2H4de0GA29UGNxtfx49YWCRk0HwHz5iuKegwwB2AS4DkA2F6VR6cAWsEQbgBqDY9vARw/ejJ+Au+LxsFcPz6BSwHpwGYPMCSS6gyAAKYhESiKMGjPgwAADopHVhn5ynEwAAAABJRU5ErkJggg=='/>" "<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: // 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/pure-min.css'>\n"
"<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.3/build/grids-responsive-min.css'>\n" "<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.3/build/grids-responsive-min.css'>\n"
...@@ -56,13 +58,21 @@ namespace web_server { ...@@ -56,13 +58,21 @@ namespace web_server {
"</head>\n" "</head>\n"
"<body>\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> sensor</p></div></div>\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" "<div class='pure-u-1'><ul class='pure-menu pure-menu-horizontal pure-menu-list'>\n"
"<li class='pure-menu-item'><a href='#graph' class='pure-menu-link'>Graph</a></li>\n"
"<li class='pure-menu-item'><a href='#table' class='pure-menu-link'>Info</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='#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" "<li class='pure-menu-item'><a href='./%s' class='pure-menu-link'>Download CSV</a></li>\n"
"</ul></div></div>\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");
body_template = body_template =
PSTR("<div class='pure-g'>\n" PSTR("<div class='pure-g'>\n"
...@@ -71,38 +81,56 @@ namespace web_server { ...@@ -71,38 +81,56 @@ namespace web_server {
"<div class='pure-g'>\n" "<div class='pure-g'>\n"
//Sensor table //Sensor table
"<table id='table' class='pure-table-striped pure-u-1 pure-u-md-1-2'>\n" "<table id='table' class='pure-table-striped pure-u-1 pure-u-md-1-2'>\n"
"<tr><th>Sensor</th><th>%s</th></tr>\n" "<tr><th colspan='2'>%s</th></tr>\n"
"<tr><td>CO<sub>2</sub> concentration</td><td>%5d ppm</td></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>Temperature</td><td>%.1f&#8451;</td></tr>\n"
"<tr><td>Humidity</td><td>%.1f%%</td></tr>\n" "<tr><td>Humidity</td><td>%.1f%%</td></tr>\n"
"<tr><td>Last measurement</td><td>%s</td></tr>\n" "<tr><td>Last measurement</td><td>%s</td></tr>\n"
"<tr><td>Measurement timestep</td><td>%5d s</td></tr>\n" "<tr><td>Measurement timestep</td><td>%5d s</td></tr>\n"
"<tr><td>Last CSV write</td><td>%s</td></tr>\n" #ifdef AMPEL_CSV
"<tr><td>CSV timestep</td><td>%5d s</td></tr>\n" "<tr><th colspan='2'>CSV</th></tr>\n"
#ifdef MQTT "<tr><td>Last write</td><td>%s</td></tr>\n"
"<tr><td>Last MQTT publish</td><td>%s</td></tr>\n" "<tr><td>Timestep</td><td>%5d s</td></tr>\n"
"<tr><td>MQTT publish 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 #endif
"<tr><td>Temperature offset</td><td>%.1fK</td></tr>\n" "<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>Local address</td><td><a href='http://%s.local/'>%s.local</a></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>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>Free heap space</td><td>%6d bytes</td></tr>\n"
"<tr><td>Available drive space</td><td>%d kB</td></tr>\n"
"<tr><td>Max loop duration</td><td>%5d ms</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>Board</td><td>%s</td></tr>\n"
"<tr><td>Uptime</td><td>%4d h %02d min %02d s</td></tr>\n" "<tr><td>Uptime</td><td>%4d h %02d min %02d s</td></tr>\n"
"</table>\n" "</table>\n"
// CSV placeholder
"<div id='log' class='pure-u-1 pure-u-md-1-2'></div>\n" "<div id='log' class='pure-u-1 pure-u-md-1-2'></div>\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');\">" "<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'/>" "<input type='submit' value='Delete CSV'/>"
"</form>\n" "</form>\n"
#endif
"</div>\n"); "</div>\n");
script_template = PSTR("<script>\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" "document.body.style.cursor = 'default';\n"
"fetch('./%s',{credentials:'include'})\n" "fetch('./%s',{credentials:'include'})\n"
// Get CSV, fill table and fill diagram
".then(response=>response.text())\n" ".then(response=>response.text())\n"
".then(csvText=>csvToTable(csvText))\n" ".then(csvText=>csvToTable(csvText))\n"
".then(htmlTable=>addLogTableToPage(htmlTable))\n" ".then(htmlTable=>addLogTableToPage(htmlTable))\n"
...@@ -139,13 +167,16 @@ namespace web_server { ...@@ -139,13 +167,16 @@ namespace web_server {
"return table;}\n" "return table;}\n"
"function addLogTableToPage(table){document.getElementById('log').appendChild(table);}\n" "function addLogTableToPage(table){document.getElementById('log').appendChild(table);}\n"
"</script>\n" "</script>\n"
#endif
"</body>\n" "</body>\n"
"</html>"); "</html>");
// Web-server // Web-server
http.on("/", handleWebServerRoot); http.on("/", handleWebServerRoot);
#ifdef AMPEL_CSV
http.on("/" + csv_writer::filename, handleWebServerCSV); http.on("/" + csv_writer::filename, handleWebServerCSV);
http.on("/delete_csv", HTTP_POST, handleDeleteCSV); http.on("/delete_csv", HTTP_POST, handleDeleteCSV);
#endif
http.onNotFound(handlePageNotFound); http.onNotFound(handlePageNotFound);
http.begin(); http.begin();
...@@ -171,14 +202,18 @@ namespace web_server { ...@@ -171,14 +202,18 @@ namespace web_server {
ss -= hh * 3600; ss -= hh * 3600;
uint8_t mm = ss / 60; uint8_t mm = ss / 60;
ss -= mm * 60; ss -= mm * 60;
uint16_t available_fs_space = csv_writer::getAvailableSpace() / 1024;
//NOTE: Splitting in multiple parts in order to use less RAM //NOTE: Splitting in multiple parts in order to use less RAM
char content[2000]; // Update if needed char content[2000]; // Update if needed
// Header size : 1383 - Body size : 1246 - Script size : 1648 // Header size : 1611 - Body size : 1800 - Script size : 1920
// Header // Header
snprintf_P(content, sizeof(content), header_template, sensor::co2, SENSOR_ID.c_str(), snprintf_P(content, sizeof(content), header_template, sensor::co2, SENSOR_ID.c_str(),
WiFi.localIP().toString().c_str(), csv_writer::filename.c_str()); WiFi.localIP().toString().c_str()
#ifdef AMPEL_CSV
, csv_writer::filename.c_str()
#endif
);
http.setContentLength(CONTENT_LENGTH_UNKNOWN); http.setContentLength(CONTENT_LENGTH_UNKNOWN);
http.send_P(200, PSTR("text/html"), content); http.send_P(200, PSTR("text/html"), content);
...@@ -186,22 +221,32 @@ namespace web_server { ...@@ -186,22 +221,32 @@ namespace web_server {
// Body // Body
snprintf_P(content, sizeof(content), body_template, SENSOR_ID.c_str(), sensor::co2, sensor::temperature, snprintf_P(content, sizeof(content), body_template, SENSOR_ID.c_str(), sensor::co2, sensor::temperature,
sensor::humidity, sensor::timestamp.c_str(), config::measurement_timestep, sensor::humidity, sensor::timestamp.c_str(), config::measurement_timestep,
csv_writer::last_successful_write.c_str(), config::csv_interval, #ifdef AMPEL_CSV
#ifdef MQTT csv_writer::last_successful_write.c_str(), config::csv_interval, csv_writer::getAvailableSpace() / 1024,
mqtt::last_successful_publish.c_str(), config::sending_interval, #endif
#ifdef AMPEL_MQTT
mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish.c_str(), config::sending_interval,
#endif
#if defined(AMPEL_LORAWAN) && defined(ESP32)
lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission.c_str(),
config::lorawan_sending_interval,
#endif #endif
config::temperature_offset, SENSOR_ID.c_str(), SENSOR_ID.c_str(), WiFi.localIP().toString().c_str(), config::temperature_offset, SENSOR_ID.c_str(), SENSOR_ID.c_str(), WiFi.localIP().toString().c_str(),
WiFi.localIP().toString().c_str(), get_free_heap_size(), available_fs_space, max_loop_duration, BOARD, hh, mm, WiFi.localIP().toString().c_str(), get_free_heap_size(), max_loop_duration, BOARD, hh, mm, ss);
ss);
http.sendContent(content); http.sendContent(content);
// Script // Script
snprintf_P(content, sizeof(content), script_template, csv_writer::filename.c_str(), SENSOR_ID.c_str()); snprintf_P(content, sizeof(content), script_template
#ifdef AMPEL_CSV
, csv_writer::filename.c_str(), SENSOR_ID.c_str()
#endif
);
http.sendContent(content); http.sendContent(content);
} }
#ifdef AMPEL_CSV
void handleWebServerCSV() { void handleWebServerCSV() {
if (!shouldBeAllowed()) { if (!shouldBeAllowed()) {
return http.requestAuthentication(DIGEST_AUTH); return http.requestAuthentication(DIGEST_AUTH);
...@@ -226,6 +271,7 @@ namespace web_server { ...@@ -226,6 +271,7 @@ namespace web_server {
http.sendHeader("Location", "/"); http.sendHeader("Location", "/");
http.send(303); http.send(303);
} }
#endif
void handlePageNotFound() { void handlePageNotFound() {
http.send(404, F("text/plain"), F("404: Not found")); http.send(404, F("text/plain"), F("404: Not found"));
......
...@@ -2,18 +2,22 @@ ...@@ -2,18 +2,22 @@
#define WEB_SERVER_H_ #define WEB_SERVER_H_
#if defined(ESP8266) #if defined(ESP8266)
# include <ESP8266WebServer.h> # include <ESP8266WebServer.h>
#endif #elif defined(ESP32)
#if defined(ESP32)
# include <WebServer.h> # include <WebServer.h>
#endif #endif
#include "config.h" #include "config.h"
#include "util.h" #include "util.h"
#include "co2_sensor.h" #include "co2_sensor.h"
#include "csv_writer.h" #ifdef AMPEL_CSV
#ifdef MQTT # include "csv_writer.h"
#endif
#ifdef AMPEL_MQTT
# include "mqtt.h" # include "mqtt.h"
#endif #endif
#ifdef AMPEL_LORAWAN
# include "lorawan.h"
#endif
namespace web_server { namespace web_server {
void initialize(); void initialize();
......
...@@ -2,13 +2,8 @@ ...@@ -2,13 +2,8 @@
namespace config { namespace config {
// WiFi config. See 'config.h' if you want to modify those values. // WiFi config. See 'config.h' if you want to modify those values.
#ifdef WIFI_SSID
const char *wifi_ssid = WIFI_SSID; const char *wifi_ssid = WIFI_SSID;
const char *wifi_password = WIFI_PASSWORD; const char *wifi_password = WIFI_PASSWORD;
#else
const char *wifi_ssid = "NO_WIFI";
const char *wifi_password = "";
#endif
#ifdef WIFI_TIMEOUT #ifdef WIFI_TIMEOUT
const uint8_t wifi_timeout = WIFI_TIMEOUT; // [s] Will try to connect during wifi_timeout seconds before failing. const uint8_t wifi_timeout = WIFI_TIMEOUT; // [s] Will try to connect during wifi_timeout seconds before failing.
...@@ -20,18 +15,11 @@ namespace config { ...@@ -20,18 +15,11 @@ namespace config {
// Initialize Wi-Fi // Initialize Wi-Fi
void WiFiConnect(const String &hostname) { void WiFiConnect(const String &hostname) {
//NOTE: WiFi Multi could allow multiple SSID and passwords. //NOTE: WiFi Multi could allow multiple SSID and passwords.
if (strcmp(config::wifi_ssid, "NO_WIFI") == 0) {
Serial.println("Please change WIFI_SSID in config.h if you want to connect.");
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
return;
}
WiFi.persistent(false); // Don't write user & password to Flash. WiFi.persistent(false); // Don't write user & password to Flash.
WiFi.mode(WIFI_STA); // Set ESP8266 to be a WiFi-client only WiFi.mode(WIFI_STA); // Set ESP to be a WiFi-client only
#if defined(ESP8266) #if defined(ESP8266)
WiFi.hostname(hostname); WiFi.hostname(hostname);
#endif #elif defined(ESP32)
#if defined(ESP32)
WiFi.setHostname(hostname.c_str()); WiFi.setHostname(hostname.c_str());
#endif #endif
...@@ -41,16 +29,17 @@ void WiFiConnect(const String &hostname) { ...@@ -41,16 +29,17 @@ void WiFiConnect(const String &hostname) {
// Wait for connection, at most wifi_timeout seconds // Wait for connection, at most wifi_timeout seconds
for (int i = 0; i <= config::wifi_timeout && (WiFi.status() != WL_CONNECTED); i++) { for (int i = 0; i <= config::wifi_timeout && (WiFi.status() != WL_CONNECTED); i++) {
LedEffects::showRainbowWheel(); led_effects::showRainbowWheel();
Serial.print("."); Serial.print(".");
} }
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
LedEffects::showKITTWheel(color::green); led_effects::showKITTWheel(color::green);
Serial.println(); Serial.println();
Serial.print("\nWiFi connected, IP address: "); Serial.print("\nWiFi connected, IP address: ");
Serial.println(WiFi.localIP()); Serial.println(WiFi.localIP());
} else { } else {
LedEffects::showKITTWheel(color::red); //TODO: Allow sensor to work as an Access Point, in order to define SSID & password?
led_effects::showKITTWheel(color::red);
Serial.println("\nConnection to WiFi failed"); Serial.println("\nConnection to WiFi failed");
} }
} }
#ifndef WIFI_UTIL_H_INCLUDED #ifndef WIFI_UTIL_H_INCLUDED
# define WIFI_UTIL_H_INCLUDED #define WIFI_UTIL_H_INCLUDED
# if defined(ESP8266)
# include <ESP8266WiFi.h>
# elif defined(ESP32)
# include <WiFi.h>
# endif
#include "led_effects.h"
#include "config.h" #include "config.h"
#include "util.h"
#include "led_effects.h"
void WiFiConnect(const String &hostname); void WiFiConnect(const String &hostname);
#endif #endif
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
; http://docs.platformio.org/page/projectconf.html ; http://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
src_dir = ./ src_dir = ampel-firmware
[env:esp8266] [env:esp8266]
platform = espressif8266 platform = espressif8266
...@@ -21,3 +21,11 @@ platform = espressif32 ...@@ -21,3 +21,11 @@ platform = espressif32
board = ttgo-lora32-v1 board = ttgo-lora32-v1
framework = arduino framework = arduino
monitor_speed = 115200 monitor_speed = 115200
lib_deps =
MCCI LoRaWAN LMIC library
build_flags =
-D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS
-D CFG_eu868=1
-D CFG_sx1276_radio=1
Supports Markdown
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