diff --git a/ampel-firmware.h b/ampel-firmware.h index cd211f49ff29f4b21e11e7047a35dd677f58af6e..9fc089a6fd44fea1a4c2203b9bf664f0c513e7b0 100644 --- a/ampel-firmware.h +++ b/ampel-firmware.h @@ -9,21 +9,25 @@ # error Missing config.h file. Please copy config.example.h to config.h. #endif -#ifdef CSV_WRITER +#ifdef AMPEL_CSV # include "csv_writer.h" #endif -#ifdef MQTT -# include "mqtt.h" + +#ifdef AMPEL_WIFI +# include "wifi_util.h" +# ifdef AMPEL_MQTT +# include "mqtt.h" +# endif +# ifdef AMPEL_HTTP +# include "web_server.h" +# endif #endif -#ifdef LORAWAN + +#ifdef AMPEL_LORAWAN # include "lorawan.h" #endif -#ifdef HTTP -# include "web_server.h" -#endif #include "util.h" -#include "wifi_util.h" #include "co2_sensor.h" #include "led_effects.h" diff --git a/ampel-firmware.ino b/ampel-firmware.ino index cfa4a2cb7bcfd5360dc0391061da875518f2fb53..0e550a3df89eff9867c1799fbac1b8663b48f53b 100644 --- a/ampel-firmware.ino +++ b/ampel-firmware.ino @@ -75,6 +75,7 @@ void setup() { Serial.print(F("Board : ")); Serial.println(BOARD); +#ifdef AMPEL_WIFI // Try to connect to Wi-Fi WiFiConnect(SENSOR_ID); @@ -82,9 +83,9 @@ void setup() { Serial.println(WiFi.status()); if (WiFi.status() == WL_CONNECTED) { -#ifdef HTTP +# ifdef AMPEL_HTTP web_server::initialize(); -#endif +# endif ntp::initialize(); @@ -95,16 +96,17 @@ void setup() { Serial.println(F("Error setting up MDNS responder!")); } -#ifdef MQTT +# ifdef AMPEL_MQTT mqtt::initialize("CO2sensors/" + SENSOR_ID); -#endif +# endif } +#endif -#ifdef CSV_WRITER +#ifdef AMPEL_CSV csv_writer::initialize(); #endif -#if defined(LORAWAN) && defined(ESP32) +#if defined(AMPEL_LORAWAN) && defined(ESP32) lorawan::initialize(); #endif } @@ -114,7 +116,7 @@ void setup() { *****************************************************************/ void loop() { -#if defined(LORAWAN) && defined(ESP32) +#if defined(AMPEL_LORAWAN) && defined(ESP32) //LMIC Library seems to be very sensitive to timing issues, so run it first. lorawan::process(); @@ -133,15 +135,15 @@ void loop() { checkFlashButton(); if (sensor::processData()) { -#ifdef CSV_WRITER +#ifdef AMPEL_CSV csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); #endif -#ifdef MQTT +#if defined(AMPEL_WIFI) && defined(AMPEL_MQTT) mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); #endif -#if defined(LORAWAN) && defined(ESP32) +#if defined(AMPEL_LORAWAN) && defined(ESP32) lorawan::preparePayloadIfTimeHasCome(sensor::co2, sensor::temperature, sensor::humidity); #endif } @@ -179,18 +181,20 @@ void checkFlashButton() { } void keepServicesAlive() { +#ifdef AMPEL_WIFI if (WiFi.status() == WL_CONNECTED) { -#if defined(ESP8266) +# 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 +# endif ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s. -#ifdef HTTP +# ifdef AMPEL_HTTP web_server::update(); -#endif -#ifdef MQTT +# endif +# ifdef AMPEL_MQTT mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s. -#endif +# endif } +#endif } diff --git a/config.public.h b/config.public.h index 072a457c79c55d33f02f2882daf09fa30750ecb8..6f5da26df7396be93b7c7aedf19fb19a079be520 100644 --- a/config.public.h +++ b/config.public.h @@ -3,12 +3,22 @@ // This file is a config template, and can be copied to config.h. Please don't save any important password in this template. +/** + * SERVICES + */ + +// Comment or remove those lines if you want to disable the corresponding services +# define AMPEL_WIFI // WiFi, and all other dependent services (MQTT, HTTP, NTP, ...) +# define AMPEL_HTTP // Should HTTP web server be started? +# define AMPEL_MQTT // Should data be sent over MQTT? +# define AMPEL_LORAWAN // Should data be sent over LoRaWAN? (Requires ESP32 + LoRa modem) +# define AMPEL_CSV // Should data be logged as CSV, on the ESP flash memory? + /** * WIFI */ -// Setting WIFI_SSID to "NO_WIFI" will disable WiFi completely, and all other dependent services (MQTT, HTTP, NTP, ...) -# define WIFI_SSID "NO_WIFI" +# define WIFI_SSID "MY_SSID" # define WIFI_PASSWORD "P4SSW0RD" # define WIFI_TIMEOUT 30 // [s] @@ -20,18 +30,10 @@ //NOTE: SCD30 timer does not seem to be very precise. Variations may occur. # define MEASUREMENT_TIMESTEP 60 // [s] Value between 2 and 1800 (range for SCD30 sensor) -// How often measurements should be sent to MQTT server? -// Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated -// Set to 0 if you want to send values after each measurement -// # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s] -# define MQTT_SENDING_INTERVAL 300 // [s] - -// Should data be written to the sensor Flash memory? Writing too often might damage the ESP memory -# define CSV_WRITER // Comment or remove this line if you want to disable CSV logging. - // How often should measurements be appended to CSV ? // Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated // Set to 0 if you want to send values after each measurement +// WARNING: Writing too often might damage the ESP memory # define CSV_INTERVAL 300 // [s] // Residual heat from CO2 sensor seems to be high enough to change the temperature reading. How much should it be offset? @@ -67,19 +69,16 @@ * available at http://local_ip, with user HTTP_USER and password HTTP_PASSWORD */ -# define HTTP // Comment or remove this line if you want to disable HTTP webserver - // Define empty strings in order to disable authentication, or remove the constants altogether. # define HTTP_USER "co2ampel" # define HTTP_PASSWORD "my_password" /** - * MQTT SERVER + * MQTT */ -# define MQTT // Comment or remove this line if you want to disable MQTT /* - * If MQTT is enabled, co2ampel will publish data every MQTT_SENDING_INTERVAL seconds. + * If AMPEL_MQTT is enabled, co2ampel will publish data every MQTT_SENDING_INTERVAL seconds. * An MQTT subscriber can then get the data from the corresponding broker, either encrypted or unencrypted: * * ⯠mosquitto_sub -h 'test.mosquitto.org' -p 8883 -t 'CO2sensors/#' --cafile mosquitto.org.crt -v @@ -101,6 +100,11 @@ */ # define ALLOW_MQTT_COMMANDS false +// How often measurements should be sent to MQTT server? +// Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated +// Set to 0 if you want to send values after each measurement +// # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s] +# define MQTT_SENDING_INTERVAL 60 // [s] # define MQTT_SERVER "test.mosquitto.org" // MQTT server URL or IP address # define MQTT_PORT 8883 # define MQTT_USER "" @@ -115,9 +119,9 @@ // * Arduino/libraries/MCCI_LoRaWAN_LMIC_library/project_config/lmic_project_config.h for Arduino IDE // * platformio.ini for PlatformIO // See https://github.com/mcci-catena/arduino-lmic#configuration for more information -// Should data be sent over LoRaWAN? // It has been tested with "TTGO ESP32 SX1276 LoRa 868" and will only work with an ESP32 + LoRa modem -# define LORAWAN +// In order to use LoRaWAN, a gateway should be close to the co2ampel, and an account, an application and a device should be registered, +// e.g. on https://www.thethingsnetwork.org/docs/applications/ // How often should measurements be sent over LoRaWAN? # define LORAWAN_SENDING_INTERVAL 300 // [s] This value should not be too low. See https://www.thethingsnetwork.org/docs/lorawan/duty-cycle.html#maximum-duty-cycle // This EUI must be in little-endian format, so least-significant-byte first. diff --git a/lorawan.cpp b/lorawan.cpp index 9765c5f729f53070b6061328e64441f8acc93b19..2e6c4078fe24e89b96a2bf0e875430e7f12baa46 100644 --- a/lorawan.cpp +++ b/lorawan.cpp @@ -184,7 +184,7 @@ namespace lorawan { void preparePayloadIfTimeHasCome(const int16_t &co2, const float &temperature, const float &humidity) { static unsigned long last_sent_at = 0; unsigned long now = seconds(); - if (now - last_sent_at > config::lorawan_sending_interval) { + if (connected && (now > last_sent_at + config::lorawan_sending_interval)) { last_sent_at = now; preparePayload(co2, temperature, humidity); } diff --git a/mqtt.cpp b/mqtt.cpp index e7e6e33f12bb6f21cbccbb1fa52566ef5b4d1782..08fe67ceb51960f3825b22049958c9ef049d1fd0 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -22,6 +22,7 @@ PubSubClient mqttClient(espClient); namespace mqtt { unsigned long last_sent_at = 0; unsigned long last_failed_at = 0; + bool connected = false; String publish_topic; const char *json_sensor_format; @@ -77,6 +78,7 @@ namespace mqtt { LedEffects::showKITTWheel(color::green, 1); } +#ifdef AMPEL_CSV void setCSVinterval(String messageString) { messageString.replace("csv ", ""); config::csv_interval = messageString.toInt(); @@ -85,6 +87,7 @@ namespace mqtt { Serial.println("s."); LedEffects::showKITTWheel(color::green, 1); } +#endif void calibrateSensorToSpecificPPM(String messageString) { messageString.replace("calibrate ", ""); @@ -149,14 +152,16 @@ namespace mqtt { calibrateSensorToSpecificPPM(messageString); } else if (messageString.startsWith("mqtt ")) { setMQTTinterval(messageString); - } else if (messageString.startsWith("csv ")) { - setCSVinterval(messageString); } else if (messageString == "publish") { Serial.println(F("Forcing MQTT publish now.")); publish(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); +#ifdef AMPEL_CSV + } else if (messageString.startsWith("csv ")) { + setCSVinterval(messageString); } else if (messageString == "format_filesystem") { FS_LIB.format(); LedEffects::showKITTWheel(color::blue, 2); +#endif } else if (messageString == "night_mode") { LedEffects::toggleNightMode(); } else if (messageString == "local_ip") { @@ -187,8 +192,9 @@ namespace mqtt { mqttClient.connect(publish_topic.c_str(), config::mqtt_user, config::mqtt_password); LedEffects::onBoardLEDOff(); - if (mqttClient.connected()) { - //TODO: Send local IP? + connected = mqttClient.connected(); + + if (connected) { if (config::allow_mqtt_commands) { char control_topic[60]; // Should be enough for "CO2sensors/ESPd03cc5/control" snprintf(control_topic, sizeof(control_topic), "%s/control", publish_topic.c_str()); diff --git a/mqtt.h b/mqtt.h index 9af46c8e60eb19650635244a7cd8ab09545b9cc1..2899a9996bbf8d2a484f5fc79b619d9647ee70a7 100644 --- a/mqtt.h +++ b/mqtt.h @@ -4,7 +4,9 @@ #include <Arduino.h> #include "config.h" #include "led_effects.h" -#include "csv_writer.h" +#ifdef AMPEL_CSV +# include "csv_writer.h" +#endif #include "co2_sensor.h" #include "src/lib/PubSubClient/src/PubSubClient.h" #include "wifi_util.h" @@ -13,6 +15,7 @@ namespace config { } namespace mqtt { extern String last_successful_publish; + extern bool connected; void initialize(String &topic); void keepConnection(); void publishIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temp, const float &hum); diff --git a/util.cpp b/util.cpp index cd7c53bd1423d8ae3056711b9474f4cb1f65c823..c32a5c561d717539d8ff0809724d381461131deb 100644 --- a/util.cpp +++ b/util.cpp @@ -39,8 +39,10 @@ namespace ntp { } void resetAmpel() { - Serial.print("Resetting"); + Serial.print(F("Resetting")); +#ifdef AMPEL_CSV FS_LIB.end(); +#endif LedEffects::LEDsOff(); delay(1000); ESP.restart(); diff --git a/util.h b/util.h index c1fc40a7b7c1e24c024469c5b6ffdecc6805cf5f..c9a8a943aaf0af03dc933e171070c56eb7a93eab 100644 --- a/util.h +++ b/util.h @@ -3,7 +3,9 @@ #include <Arduino.h> #include "config.h" #include "wifi_util.h" // To get MAC -#include "csv_writer.h" // To close filesystem before reset +#ifdef AMPEL_CSV +# include "csv_writer.h" // To close filesystem before reset +#endif #include <WiFiUdp.h> //required for NTP #include "src/lib/NTPClient-master/NTPClient.h" // NTP diff --git a/web_server.cpp b/web_server.cpp index 812dbcce40e8e6c730f43e035a99242c2ae9875b..7501263d6abebc3c0849726ecf65eb1cce6f28c9 100644 --- a/web_server.cpp +++ b/web_server.cpp @@ -21,9 +21,12 @@ namespace web_server { const char *body_template; const char *script_template; void handleWebServerRoot(); - void handleWebServerCSV(); void handlePageNotFound(); + +#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 @@ -58,7 +61,7 @@ namespace web_server { "<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='#table' class='pure-menu-link'>Info</a></li>\n" -#ifdef CSV_WRITER +#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" @@ -84,17 +87,19 @@ namespace web_server { "<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 CSV_WRITER +#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 MQTT +#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(LORAWAN) && defined(ESP32) +#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" @@ -106,13 +111,12 @@ namespace web_server { "<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>Available drive space</td><td>%d kB</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>Uptime</td><td>%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" -#ifdef CSV_WRITER +#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" @@ -123,7 +127,7 @@ namespace web_server { 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 CSV_WRITER +#ifdef AMPEL_CSV "<script>\n" "document.body.style.cursor = 'default';\n" "fetch('./%s',{credentials:'include'})\n" @@ -169,7 +173,7 @@ namespace web_server { // Web-server http.on("/", handleWebServerRoot); -#ifdef CSV_WRITER +#ifdef AMPEL_CSV http.on("/" + csv_writer::filename, handleWebServerCSV); http.on("/delete_csv", HTTP_POST, handleDeleteCSV); #endif @@ -198,7 +202,6 @@ namespace web_server { ss -= hh * 3600; uint8_t mm = ss / 60; ss -= mm * 60; - uint16_t available_fs_space = csv_writer::getAvailableSpace() / 1024; //NOTE: Splitting in multiple parts in order to use less RAM char content[2000]; // Update if needed @@ -206,7 +209,11 @@ namespace web_server { // Header 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.send_P(200, PSTR("text/html"), content); @@ -214,28 +221,32 @@ namespace web_server { // Body snprintf_P(content, sizeof(content), body_template, SENSOR_ID.c_str(), sensor::co2, sensor::temperature, sensor::humidity, sensor::timestamp.c_str(), config::measurement_timestep, -#ifdef CSV_WRITER - csv_writer::last_successful_write.c_str(), config::csv_interval, +#ifdef AMPEL_CSV + csv_writer::last_successful_write.c_str(), config::csv_interval, csv_writer::getAvailableSpace() / 1024, #endif -#ifdef MQTT - mqtt::last_successful_publish.c_str(), config::sending_interval, +#ifdef AMPEL_MQTT + mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish.c_str(), config::sending_interval, #endif -#if defined(LORAWAN) && defined(ESP32) +#if defined(AMPEL_LORAWAN) && defined(ESP32) lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission.c_str(), config::lorawan_sending_interval, #endif 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, - ss); + WiFi.localIP().toString().c_str(), get_free_heap_size(), max_loop_duration, BOARD, hh, mm, ss); http.sendContent(content); // 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); } +#ifdef AMPEL_CSV void handleWebServerCSV() { if (!shouldBeAllowed()) { return http.requestAuthentication(DIGEST_AUTH); @@ -260,6 +271,7 @@ namespace web_server { http.sendHeader("Location", "/"); http.send(303); } +#endif void handlePageNotFound() { http.send(404, F("text/plain"), F("404: Not found")); diff --git a/web_server.h b/web_server.h index d453e63fa404f025b85e98ad8dee4538bac364cf..71c17f0578ac1616f464d0456f25afcfa5930c72 100644 --- a/web_server.h +++ b/web_server.h @@ -9,13 +9,13 @@ #include "config.h" #include "util.h" #include "co2_sensor.h" -#ifdef CSV_WRITER +#ifdef AMPEL_CSV # include "csv_writer.h" #endif -#ifdef MQTT +#ifdef AMPEL_MQTT # include "mqtt.h" #endif -#ifdef LORAWAN +#ifdef AMPEL_LORAWAN # include "lorawan.h" #endif diff --git a/wifi_util.cpp b/wifi_util.cpp index f8c0230c0ffefeb06ee5ff3bd31a0adfa7a99898..2da3d1884f19e9c9a0cdaeed34439d244ab99e06 100644 --- a/wifi_util.cpp +++ b/wifi_util.cpp @@ -2,16 +2,11 @@ namespace config { // WiFi config. See 'config.h' if you want to modify those values. -#ifdef WIFI_SSID const char *wifi_ssid = WIFI_SSID; const char *wifi_password = WIFI_PASSWORD; -#else - const char *wifi_ssid = "NO_WIFI"; - const char *wifi_password = ""; -#endif #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. #else const uint8_t wifi_timeout = 60; // [s] Will try to connect during wifi_timeout seconds before failing. #endif @@ -20,14 +15,8 @@ namespace config { // Initialize Wi-Fi void WiFiConnect(const String &hostname) { //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.mode(WIFI_STA); // Set ESP8266 to be a WiFi-client only + WiFi.mode(WIFI_STA); // Set ESP8266 to be a WiFi-client only #if defined(ESP8266) WiFi.hostname(hostname); #elif defined(ESP32)