diff --git a/ampel-firmware/ampel-firmware.ino b/ampel-firmware/ampel-firmware.ino index facb1e3874f36a28d389f9f01c403278691a8d7c..e8eef2f414953c155f0f2e781d0da75f35623c97 100644 --- a/ampel-firmware/ampel-firmware.ino +++ b/ampel-firmware/ampel-firmware.ino @@ -83,12 +83,12 @@ void setup() { sensor::initialize(); Serial.print(F("Sensor ID: ")); - Serial.println(SENSOR_ID); + Serial.println(ampel.sensorId); Serial.print(F("Board : ")); - Serial.println(BOARD); + Serial.println(ampel.board); #ifdef AMPEL_WIFI - WiFiConnect(SENSOR_ID); + WiFiConnect(ampel.sensorId); Serial.print(F("WiFi - Status: ")); Serial.println(WiFi.status()); @@ -100,7 +100,7 @@ void setup() { ntp::initialize(); - if (MDNS.begin(SENSOR_ID.c_str())) { // Start the mDNS responder for SENSOR_ID.local + if (MDNS.begin(ampel.sensorId.c_str())) { // Start the mDNS responder for SENSOR_ID.local MDNS.addService("http", "tcp", 80); Serial.println(F("mDNS responder started")); } else { @@ -108,7 +108,7 @@ void setup() { } # ifdef AMPEL_MQTT - mqtt::initialize("CO2sensors/" + SENSOR_ID); + mqtt::initialize("CO2sensors/" + ampel.sensorId); # endif } #endif @@ -162,10 +162,10 @@ void loop() { } uint32_t duration = millis() - t0; - if (duration > max_loop_duration) { - max_loop_duration = duration; + if (duration > ampel.max_loop_duration) { + ampel.max_loop_duration = duration; Serial.print(F("Debug - Max loop duration : ")); - Serial.print(max_loop_duration); + Serial.print(ampel.max_loop_duration); Serial.println(F(" ms.")); } } diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 430b945575fe37c115351a741cf90caffa549e64..39c90baab92ec423fc28e0c68963924791557bda 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -14,7 +14,7 @@ namespace config { #else const float temperature_offset = -3.0; // [K] Temperature measured by sensor is usually at least 3K too high. #endif - const bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false] + bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false] } namespace sensor { @@ -22,7 +22,7 @@ namespace sensor { uint16_t co2 = 0; float temperature = 0; float humidity = 0; - String timestamp = ""; + char timestamp[23]; int16_t stable_measurements = 0; uint32_t waiting_color = color::blue; bool should_calibrate = false; @@ -71,17 +71,15 @@ namespace sensor { Serial.print(F("Auto-calibration is ")); Serial.println(config::auto_calibrate_sensor ? "ON." : "OFF."); - sensor_console::defineIntCommand("co2", setCO2forDebugging, " 1500 (Sets co2 level, for debugging purposes)"); - sensor_console::defineIntCommand("timer", setTimer, " 30 (Sets measurement interval, in s)"); - sensor_console::defineCommand("calibrate", startCalibrationProcess, " (Starts calibration process)"); + sensor_console::defineIntCommand("co2", setCO2forDebugging, F(" 1500 (Sets co2 level, for debugging purposes)")); + sensor_console::defineIntCommand("timer", setTimer, F(" 30 (Sets measurement interval, in s)")); + sensor_console::defineCommand("calibrate", startCalibrationProcess, F(" (Starts calibration process)")); sensor_console::defineIntCommand("calibrate", calibrateSensorToSpecificPPM, - " 600 (Starts calibration process, to given ppm)"); + F(" 600 (Starts calibration process, to given ppm)")); sensor_console::defineIntCommand("calibrate!", calibrateSensorRightNow, - " 600 (Calibrates right now, to given ppm)"); - sensor_console::defineCommand("reset", []() { - ESP.restart(); - }, " (Restarts the sensor)"); - sensor_console::defineCommand("night_mode", led_effects::toggleNightMode, " (Toggles night mode on/off)"); + F(" 600 (Calibrates right now, to given ppm)")); + sensor_console::defineIntCommand("auto_calibrate", setAutoCalibration, + F(" 0/1 (Disables/enables autocalibration)")); } //NOTE: should timer deviation be used to adjust measurement_timestep? @@ -168,7 +166,7 @@ namespace sensor { if (freshData) { // checkTimerDeviation(); - timestamp = ntp::getLocalTime(); + ntp::getLocalTime(timestamp); co2 = scd30.getCO2(); temperature = scd30.getTemperature(); humidity = scd30.getHumidity(); @@ -212,6 +210,13 @@ namespace sensor { Serial.println(co2); } + void setAutoCalibration(int32_t autoCalibration) { + config::auto_calibrate_sensor = autoCalibration; + scd30.setAutoSelfCalibration(autoCalibration); + Serial.print(F("Setting auto-calibration to : ")); + Serial.println(autoCalibration ? F("On.") : F("Off.")); + } + void setTimer(int32_t timestep) { if (timestep >= 2 && timestep <= 1800) { Serial.print(F("Setting Measurement Interval to : ")); diff --git a/ampel-firmware/co2_sensor.h b/ampel-firmware/co2_sensor.h index dd18bcf312048448ea665ad61b12ccb14b6f27c4..275c6e755d84681230101df08bd5bd4060528bd1 100644 --- a/ampel-firmware/co2_sensor.h +++ b/ampel-firmware/co2_sensor.h @@ -12,7 +12,7 @@ namespace config { extern uint16_t measurement_timestep; // [s] Value between 2 and 1800 (range for SCD30 sensor) - extern const bool auto_calibrate_sensor; // [true / false] + extern bool auto_calibrate_sensor; // [true / false] extern uint16_t co2_calibration_level; // [ppm] extern const float temperature_offset; // [K] Sign isn't relevant. } @@ -22,7 +22,7 @@ namespace sensor { extern uint16_t co2; extern float temperature; extern float humidity; - extern String timestamp; + extern char timestamp[23]; void initialize(); bool processData(); @@ -32,5 +32,6 @@ namespace sensor { void setTimer(int32_t timestep); void calibrateSensorToSpecificPPM(int32_t calibrationLevel); void calibrateSensorRightNow(int32_t calibrationLevel); + void setAutoCalibration(int32_t autoCalibration); } #endif diff --git a/ampel-firmware/config.public.h b/ampel-firmware/config.public.h index feb7f4e612f0fecb0b268f58b7b8fbdee04cb6ff..1ca67666cb3f76cb20ca1a9d03dc1b38d6bc3558 100644 --- a/ampel-firmware/config.public.h +++ b/ampel-firmware/config.public.h @@ -144,7 +144,7 @@ */ # define NTP_SERVER "pool.ntp.org" -# define UTC_OFFSET_IN_SECONDS 3600 // [s] 3600 for UTC+1 +# define UTC_OFFSET_IN_SECONDS 7200 // [s] 3600 for UTC+1, 7200 for UTC+1 and daylight saving time /** * Others diff --git a/ampel-firmware/csv_writer.cpp b/ampel-firmware/csv_writer.cpp index 2a7f462a4a750ed7565a8ff8318d6a608cbf12a0..199d9f39f38e99cd6e641a42e5690e07439522f0 100644 --- a/ampel-firmware/csv_writer.cpp +++ b/ampel-firmware/csv_writer.cpp @@ -6,7 +6,7 @@ namespace config { } namespace csv_writer { unsigned long last_written_at = 0; - String last_successful_write = ""; + char last_successful_write[23]; #if defined(ESP8266) /** @@ -78,7 +78,7 @@ namespace csv_writer { } #endif - const String filename = "/" + SENSOR_ID + ".csv"; + const String filename = "/" + ampel.sensorId + ".csv"; int getAvailableSpace() { return getTotalSpace() - getUsedSpace(); @@ -115,9 +115,9 @@ namespace csv_writer { showFilesystemContent(); Serial.println(); - sensor_console::defineIntCommand("csv", setCSVinterval, " 60 (Sets CSV writing interval, in s)"); - sensor_console::defineCommand("format_filesystem", formatFilesystem, " (Deletes the whole filesystem)"); - sensor_console::defineCommand("show_csv", showCSVContent, " (Displays the complete CSV file on Serial)"); + sensor_console::defineIntCommand("csv", setCSVinterval, F(" 60 (Sets CSV writing interval, in s)")); + sensor_console::defineCommand("format_filesystem", formatFilesystem, F(" (Deletes the whole filesystem)")); + sensor_console::defineCommand("show_csv", showCSVContent, F(" (Displays the complete CSV file on Serial)")); } File openOrCreate() { @@ -132,11 +132,11 @@ namespace csv_writer { return csv_file; } - void log(const String &timeStamp, const int16_t &co2, const float &temperature, const float &humidity) { + void log(const char *timestamp, const int16_t &co2, const float &temperature, const float &humidity) { led_effects::onBoardLEDOn(); File csv_file = openOrCreate(); char csv_line[42]; - snprintf(csv_line, sizeof(csv_line), "%s;%d;%.1f;%.1f\r\n", timeStamp.c_str(), co2, temperature, humidity); + snprintf(csv_line, sizeof(csv_line), "%s;%d;%.1f;%.1f\r\n", timestamp, co2, temperature, humidity); if (csv_file) { size_t written_bytes = csv_file.print(csv_line); csv_file.close(); @@ -145,7 +145,7 @@ namespace csv_writer { } else { Serial.print(F("CSV - Wrote : ")); Serial.print(csv_line); - last_successful_write = ntp::getLocalTime(); + ntp::getLocalTime(last_successful_write); } updateFsInfo(); delay(50); @@ -156,7 +156,7 @@ namespace csv_writer { led_effects::onBoardLEDOff(); } - void logIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temperature, const float &humidity) { + void logIfTimeHasCome(const char *timeStamp, const int16_t &co2, const float &temperature, const float &humidity) { unsigned long now = seconds(); if (now - last_written_at > config::csv_interval) { last_written_at = now; diff --git a/ampel-firmware/csv_writer.h b/ampel-firmware/csv_writer.h index 080d5cdcc2e4b2e5aa17617118120f5c5213dac6..ab4a87b2972e8b3ae597e637a6f309da0d829658 100644 --- a/ampel-firmware/csv_writer.h +++ b/ampel-firmware/csv_writer.h @@ -20,9 +20,9 @@ namespace config { extern uint16_t csv_interval; // [s] } namespace csv_writer { - extern String last_successful_write; + extern char last_successful_write[23]; void initialize(); - void logIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temperature, const float &humidity); + void logIfTimeHasCome(const char* timestamp, const int16_t &co2, const float &temperature, const float &humidity); int getAvailableSpace(); extern const String filename; diff --git a/ampel-firmware/led_effects.cpp b/ampel-firmware/led_effects.cpp index 51ba98b96c3e19df4737c0ef77900fa450e6e231..ee0fccde5222ecf935dc444d86ed8663d5291a77 100644 --- a/ampel-firmware/led_effects.cpp +++ b/ampel-firmware/led_effects.cpp @@ -74,6 +74,7 @@ namespace led_effects { pixels.begin(); pixels.setBrightness(config::max_brightness); LEDsOff(); + sensor_console::defineCommand("night_mode", toggleNightMode, F(" (Toggles night mode on/off)")); } void toggleNightMode() { diff --git a/ampel-firmware/led_effects.h b/ampel-firmware/led_effects.h index 3deb809940e61c9d042ddc52614f7b0ec16a08ca..96367ef5f2909d38377e2441f91d4e2e1abe2198 100644 --- a/ampel-firmware/led_effects.h +++ b/ampel-firmware/led_effects.h @@ -2,6 +2,7 @@ #define LED_EFFECTS_H_INCLUDED #include <Arduino.h> #include "config.h" +#include "sensor_console.h" // Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip) // https://github.com/adafruit/Adafruit_NeoPixel diff --git a/ampel-firmware/lorawan.cpp b/ampel-firmware/lorawan.cpp index 70a02e1c8325a21a42f63402956d497264baf3d8..784b46fb61b25bc8d3868a3a541baa14f241150a 100644 --- a/ampel-firmware/lorawan.cpp +++ b/ampel-firmware/lorawan.cpp @@ -33,7 +33,7 @@ void os_getDevKey(u1_t *buf) { namespace lorawan { bool waiting_for_confirmation = false; bool connected = false; - String last_transmission = ""; + char last_transmission[23] = ""; void initialize() { Serial.println(F("Starting LoRaWAN. Frequency plan : " LMIC_FREQUENCY_PLAN " MHz.")); @@ -47,7 +47,7 @@ namespace lorawan { LMIC_reset(); // Join, but don't send anything yet. LMIC_startJoining(); - sensor_console::defineIntCommand("lora", setLoRaInterval, " 300 (Sets LoRaWAN sending interval, in s)"); + sensor_console::defineIntCommand("lora", setLoRaInterval, F(" 300 (Sets LoRaWAN sending interval, in s)")); } // Checks if OTAA is connected, or if payload should be sent. @@ -65,8 +65,10 @@ namespace lorawan { } void onEvent(ev_t ev) { + char current_time[23]; + ntp::getLocalTime(current_time); Serial.print("LoRa - "); - Serial.print(ntp::getLocalTime()); + Serial.print(current_time); Serial.print(" - "); switch (ev) { case EV_JOINING: @@ -113,7 +115,7 @@ namespace lorawan { Serial.println(F("EV_REJOIN_FAILED")); break; case EV_TXCOMPLETE: - last_transmission = ntp::getLocalTime(); + ntp::getLocalTime(last_transmission); Serial.println(F("EV_TXCOMPLETE")); break; case EV_TXSTART: diff --git a/ampel-firmware/lorawan.h b/ampel-firmware/lorawan.h index 6cf1226f72f7385e075ff387c160d0dbdefa2ffd..45f462b2ef761066797ccd2ff114762088b5c628 100644 --- a/ampel-firmware/lorawan.h +++ b/ampel-firmware/lorawan.h @@ -39,7 +39,7 @@ namespace config { namespace lorawan { extern bool waiting_for_confirmation; extern bool connected; - extern String last_transmission; + extern char last_transmission[]; void initialize(); void process(); void preparePayloadIfTimeHasCome(const int16_t &co2, const float &temp, const float &hum); diff --git a/ampel-firmware/mqtt.cpp b/ampel-firmware/mqtt.cpp index 516090744a5ac7c8cfed9cfdee02391b5bb78d4e..fda75272a905792d62cb38f1e43b53c6bfd464b7 100644 --- a/ampel-firmware/mqtt.cpp +++ b/ampel-firmware/mqtt.cpp @@ -25,7 +25,7 @@ namespace mqtt { String publish_topic; const char *json_sensor_format; - String last_successful_publish = ""; + char last_successful_publish[23] = ""; void initialize(String &topic) { json_sensor_format = PSTR("{\"time\":\"%s\", \"co2\":%d, \"temp\":%.1f, \"rh\":%.1f}"); @@ -36,22 +36,22 @@ namespace mqtt { // mqttClient.setSocketTimeout(config::mqtt_timeout); //NOTE: somehow doesn't seem to have any effect on connect() mqttClient.setServer(config::mqtt_server, config::mqtt_port); - sensor_console::defineIntCommand("mqtt", setMQTTinterval, " 60 (Sets MQTT sending interval, in s)"); + sensor_console::defineIntCommand("mqtt", setMQTTinterval, F(" 60 (Sets MQTT sending interval, in s)")); sensor_console::defineCommand("local_ip", sendInfoAboutLocalNetwork, - " (Sends local IP and SSID via MQTT. Can be useful to find sensor)"); + F(" (Sends local IP and SSID via MQTT. Can be useful to find sensor)")); } - void publish(const String ×tamp, int16_t co2, float temperature, float humidity) { + void publish(const char *timestamp, int16_t co2, float temperature, float humidity) { if (WiFi.status() == WL_CONNECTED && mqttClient.connected()) { led_effects::onBoardLEDOn(); Serial.print(F("MQTT - Publishing message ... ")); char payload[75]; // Should be enough for json... - snprintf(payload, sizeof(payload), json_sensor_format, timestamp.c_str(), co2, temperature, humidity); + snprintf(payload, sizeof(payload), json_sensor_format, timestamp, co2, temperature, humidity); // Topic is the same as clientID. e.g. 'CO2sensors/ESP3d03da' if (mqttClient.publish(publish_topic.c_str(), payload)) { Serial.println(F("OK")); - last_successful_publish = ntp::getLocalTime(); + ntp::getLocalTime(last_successful_publish); } else { Serial.println(F("Failed.")); } @@ -120,12 +120,12 @@ namespace mqtt { } } - void publishIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temp, const float &hum) { + void publishIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temp, const float &hum) { // Send message via MQTT according to sending interval unsigned long now = seconds(); if (now - last_sent_at > config::mqtt_sending_interval) { last_sent_at = now; - publish(timeStamp, co2, temp, hum); + publish(timestamp, co2, temp, hum); } } diff --git a/ampel-firmware/mqtt.h b/ampel-firmware/mqtt.h index aaaa95621d10cdf0a8608700654a89b360563989..9167c47dda720528f138ff048630d399e6656984 100644 --- a/ampel-firmware/mqtt.h +++ b/ampel-firmware/mqtt.h @@ -11,11 +11,11 @@ namespace config { extern uint16_t mqtt_sending_interval; // [s] } namespace mqtt { - extern String last_successful_publish; + extern char 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); + void publishIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temp, const float &hum); void setMQTTinterval(int32_t sending_interval); void sendInfoAboutLocalNetwork(); diff --git a/ampel-firmware/sensor_console.cpp b/ampel-firmware/sensor_console.cpp index ec55534d0ed69c96a820047939ff0b1285db4faa..fd29d4acc03cfe3bf55cb90ce26d8b245bbccc69 100644 --- a/ampel-firmware/sensor_console.cpp +++ b/ampel-firmware/sensor_console.cpp @@ -19,7 +19,8 @@ namespace sensor_console { Command commands[MAX_COMMANDS]; //NOTE: Probably possible to DRY (with templates?) - void defineCommand(const char *name, void (*function)(void), const char *doc) { + void defineCommand(const char *name, void (*function)(void), const __FlashStringHelper *doc_fstring) { + const char *doc = (const char PROGMEM*) doc_fstring; if (commands_count < MAX_COMMANDS) { commands[commands_count].name = name; commands[commands_count].voidFunction = function; @@ -31,7 +32,8 @@ namespace sensor_console { } } - void defineIntCommand(const char *name, void (*function)(int32_t), const char *doc) { + void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring) { + const char *doc = (const char PROGMEM*) doc_fstring; if (commands_count < MAX_COMMANDS) { commands[commands_count].name = name; commands[commands_count].intFunction = function; @@ -50,13 +52,11 @@ namespace sensor_console { uint8_t parseCommand(const char *command, char *function_name, int32_t &argument) { char split_command[MAX_COMMAND_SIZE]; strlcpy(split_command, command, MAX_COMMAND_SIZE); - Serial.print(F("Received : '")); - Serial.print(command); - Serial.println("'"); char *arg; char *part1; part1 = strtok(split_command, " "); if (!part1) { + Serial.println(F("Received empty command")); // Empty string return 1; } @@ -126,7 +126,6 @@ namespace sensor_console { Serial.print(commands[i].doc); Serial.println("."); } - led_effects::showKITTWheel(color::red, 1); } /* @@ -143,18 +142,20 @@ namespace sensor_console { Serial.print(F("Calling : ")); Serial.print(function_name); if (has_argument) { - Serial.print("("); + Serial.print(F("(")); Serial.print(argument); - Serial.println(")"); + Serial.println(F(")")); commands[i].intFunction(argument); } else { - Serial.println("()"); + Serial.println(F("()")); commands[i].voidFunction(); } return; } } - Serial.println(F("Message not supported. Available commands :")); + Serial.print(F("'")); + Serial.print(command); + Serial.println(F("' not supported. Available commands :")); listAvailableCommands(); } } diff --git a/ampel-firmware/sensor_console.h b/ampel-firmware/sensor_console.h index 8afa40c34838b2c321b55546a75a34d74360ab6b..e15241416c4ca45d6ad83992cf9609af51f0dc43 100644 --- a/ampel-firmware/sensor_console.h +++ b/ampel-firmware/sensor_console.h @@ -1,5 +1,6 @@ +#ifndef SENSOR_CONSOLE_H_INCLUDED +#define SENSOR_CONSOLE_H_INCLUDED #include <Arduino.h> -#include "led_effects.h" /** Other scripts can use this namespace, in order to define commands, via callbacks. * Those callbacks can then be used to send commands to the sensor (reset, calibrate, night mode, ...) @@ -9,6 +10,8 @@ namespace sensor_console { void processSerialInput(const byte in_byte); void runCommand(const char *command); - void defineIntCommand(const char *command, void (*function)(int32_t), const char *doc); - void defineCommand(const char *command, void (*function)(void), const char *doc); + void defineIntCommand(const char *command, void (*function)(int32_t), const __FlashStringHelper *ifsh); + void defineCommand(const char *command, void (*function)(void), const __FlashStringHelper *ifsh); } + +#endif diff --git a/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp b/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp index 048a82a4bcec1d3885070fbb83db764f60369080..1b52de5af6eefae690044fa9138222fb3aa78d1b 100644 --- a/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp +++ b/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp @@ -152,19 +152,17 @@ int NTPClient::getSeconds() { return (this->getEpochTime() % 60); } -String NTPClient::getFormattedTime(unsigned long secs) { +void NTPClient::getFormattedTime(char *formatted_time, unsigned long secs) { unsigned long rawTime = secs ? secs : this->getEpochTime(); unsigned int hours = (rawTime % 86400L) / 3600; unsigned int minutes = (rawTime % 3600) / 60; unsigned int seconds = rawTime % 60; - char formatted_time[9]; - snprintf(formatted_time, sizeof(formatted_time), "%02d:%02d:%02d", hours, minutes, seconds); - return String(formatted_time); + snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds); } // Based on https://github.com/PaulStoffregen/Time/blob/master/Time.cpp -String NTPClient::getFormattedDate(unsigned long secs) { +void NTPClient::getFormattedDate(char *formatted_date, unsigned long secs) { unsigned long rawTime = (secs ? secs : this->getEpochTime()) / 86400L; // in days unsigned long days = 0, year = 1970; uint8_t month; @@ -187,11 +185,9 @@ String NTPClient::getFormattedDate(unsigned long secs) { month++; // jan is month 1 rawTime++; // first day is day 1 - char formatted_date[23]; - snprintf(formatted_date, sizeof(formatted_date), "%4lu-%02d-%02lu %s%+03d", - year, month, rawTime, this->getFormattedTime(secs).c_str(), this->_timeOffset / 3600); - - return String(formatted_date); + char formatted_time[9]; + this->getFormattedTime(formatted_time, secs); + snprintf(formatted_date, 23, "%4lu-%02d-%02lu %s%+03d", year, month, rawTime, formatted_time, this->_timeOffset / 3600); } void NTPClient::end() { diff --git a/ampel-firmware/src/lib/NTPClient-master/NTPClient.h b/ampel-firmware/src/lib/NTPClient-master/NTPClient.h index ad450706490679ee8a459c5471efec167e419d21..3349dbb7df998fc931874c3f1ec86cda07fc3f80 100644 --- a/ampel-firmware/src/lib/NTPClient-master/NTPClient.h +++ b/ampel-firmware/src/lib/NTPClient-master/NTPClient.h @@ -80,7 +80,7 @@ class NTPClient { /** * @return secs argument (or 0 for current time) formatted like `hh:mm:ss` */ - String getFormattedTime(unsigned long secs = 0); + void getFormattedTime(char *formatted_time, unsigned long secs = 0); /** * @return time in seconds since Jan. 1, 1970 @@ -91,7 +91,7 @@ class NTPClient { * @return secs argument (or 0 for current date) formatted to ISO 8601 * like `2004-02-12T15:19:21+00:00` */ - String getFormattedDate(unsigned long secs = 0); + void getFormattedDate(char *formatted_date, unsigned long secs = 0); /** * Stops the underlying UDP client diff --git a/ampel-firmware/util.cpp b/ampel-firmware/util.cpp index 31df19dd856e5c3f17b5eb929979660eb27db715..8a593666ae0b4fa10fd9568ad29447a8680c0812 100644 --- a/ampel-firmware/util.cpp +++ b/ampel-firmware/util.cpp @@ -5,6 +5,14 @@ namespace config { const long utc_offset_in_seconds = UTC_OFFSET_IN_SECONDS; // UTC+1 } +#if defined(ESP8266) +const char *current_board = "ESP8266"; +#elif defined(ESP32) +const char *current_board = "ESP32"; +#else +const char *current_board = "UNKNOWN"; +#endif + // Get last 3 bytes of ESP MAC (worldwide unique) String macToID() { uint8_t mac[6]; @@ -24,22 +32,51 @@ String macToID() { namespace ntp { WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, config::ntp_server, config::utc_offset_in_seconds, 60000UL); + bool connected_at_least_once = false; void initialize() { timeClient.begin(); } void update() { - timeClient.update(); + connected_at_least_once |= timeClient.update(); + } + + void getLocalTime(char *timestamp) { + timeClient.getFormattedDate(timestamp); } - String getLocalTime() { - return timeClient.getFormattedDate(); + void setLocalTime(int32_t unix_seconds) { + char time[23]; + timeClient.getFormattedDate(time); + Serial.print(F("Current time : ")); + Serial.println(time); + if (connected_at_least_once) { + Serial.println(F("NTP update already happened. Not changing anything.")); + return; + } + Serial.print(F("Setting UNIX time to : ")); + Serial.println(unix_seconds); + timeClient.setEpochTime(unix_seconds - seconds()); + timeClient.getFormattedDate(time); + Serial.print(F("Current time : ")); + Serial.println(time); } } -uint32_t max_loop_duration = 0; +void Ampel::showFreeSpace() { + Serial.print(F("Free heap space : ")); + Serial.print(get_free_heap_size()); + Serial.println(F(" bytes.")); +} + +Ampel::Ampel() : + board(current_board), sensorId("ESP" + macToID()), max_loop_duration(0) { + sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F(" 1618829570 (Sets time to the given UNIX time)")); + sensor_console::defineCommand("free", Ampel::showFreeSpace, F(" (Displays available heap space)")); + sensor_console::defineCommand("reset", []() { + ESP.restart(); + }, F(" (Restarts the sensor)")); +} -//FIXME: Remove every instance of Strings, to avoid heap fragmentation problems. (Start: "Free heap space : 17104 bytes") -// See more https://cpp4arduino.com/2020/02/07/how-to-format-strings-without-the-string-class.html -const String SENSOR_ID = "ESP" + macToID(); +Ampel ampel; diff --git a/ampel-firmware/util.h b/ampel-firmware/util.h index 2997933480b06db67b18a09bafa2d156d8b7e7b5..b99a5d52d6c137c92372223da317ae7e2bf2ad64 100644 --- a/ampel-firmware/util.h +++ b/ampel-firmware/util.h @@ -2,26 +2,23 @@ #define AMPEL_UTIL_H_INCLUDED #include <Arduino.h> #include "config.h" +#include "sensor_console.h" #include <WiFiUdp.h> // required for NTP #include "src/lib/NTPClient-master/NTPClient.h" // NTP #if defined(ESP8266) -# define BOARD "ESP8266" # include <ESP8266WiFi.h> // required to get MAC address # define get_free_heap_size() system_get_free_heap_size() #elif defined(ESP32) -# define BOARD "ESP32" # include <WiFi.h> // required to get MAC address # define get_free_heap_size() esp_get_free_heap_size() -#else -# define BOARD "Unknown" #endif namespace ntp { void initialize(); void update(); - String getLocalTime(); + void getLocalTime(char *timestamp); } namespace util { @@ -35,10 +32,18 @@ namespace util { return b > a ? b : a; } } +class Ampel { +private: + static void showFreeSpace(); +public: + const char *board; + const String sensorId; + uint32_t max_loop_duration; + Ampel(); +}; //NOTE: Only use seconds() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over. #define seconds() (millis() / 1000UL) -extern uint32_t max_loop_duration; -const extern String SENSOR_ID; +extern Ampel ampel; #endif diff --git a/ampel-firmware/web_server.cpp b/ampel-firmware/web_server.cpp index c9dfeab5e2546703dc2968f5eefa45799d86282a..ee20bf43cf57c5171a0b28a5915354c9cdf8dc48 100644 --- a/ampel-firmware/web_server.cpp +++ b/ampel-firmware/web_server.cpp @@ -184,7 +184,7 @@ namespace web_server { http.begin(); Serial.print(F("You can access this sensor via http://")); - Serial.print(SENSOR_ID); + Serial.print(ampel.sensorId); Serial.print(F(".local (might be unstable) or http://")); Serial.println(WiFi.localIP()); } @@ -212,7 +212,7 @@ namespace web_server { 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, SENSOR_ID.c_str(), + snprintf_P(content, sizeof(content), header_template, sensor::co2, ampel.sensorId.c_str(), WiFi.localIP().toString().c_str() #ifdef AMPEL_CSV , csv_writer::filename.c_str() @@ -225,21 +225,21 @@ namespace web_server { http.send_P(200, PSTR("text/html"), content); // 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, + snprintf_P(content, sizeof(content), body_template, ampel.sensorId.c_str(), sensor::co2, sensor::temperature, + sensor::humidity, sensor::timestamp, config::measurement_timestep, #ifdef AMPEL_CSV - csv_writer::last_successful_write.c_str(), config::csv_interval, csv_writer::getAvailableSpace() / 1024, + csv_writer::last_successful_write, config::csv_interval, csv_writer::getAvailableSpace() / 1024, #endif #ifdef AMPEL_MQTT - mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish.c_str(), config::mqtt_sending_interval, + 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.c_str(), + lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission, config::lorawan_sending_interval, #endif - config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", SENSOR_ID.c_str(), SENSOR_ID.c_str(), - WiFi.localIP().toString().c_str(), WiFi.localIP().toString().c_str(), get_free_heap_size(), max_loop_duration, - BOARD, dd, hh, mm, ss); + config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId.c_str(), + ampel.sensorId.c_str(), WiFi.localIP().toString().c_str(), WiFi.localIP().toString().c_str(), + get_free_heap_size(), ampel.max_loop_duration, ampel.board, dd, hh, mm, ss); Serial.print(F(" - Body size : ")); http.sendContent(content); @@ -248,7 +248,7 @@ namespace web_server { // Script snprintf_P(content, sizeof(content), script_template #ifdef AMPEL_CSV - , csv_writer::filename.c_str(), SENSOR_ID.c_str() + , csv_writer::filename.c_str(), ampel.sensorId.c_str() #endif );