diff --git a/ampel-firmware/ampel-firmware.ino b/ampel-firmware/ampel-firmware.ino index e8eef2f414953c155f0f2e781d0da75f35623c97..df16f22c271ec34490249651afde879be634aa12 100644 --- a/ampel-firmware/ampel-firmware.ino +++ b/ampel-firmware/ampel-firmware.ino @@ -87,8 +87,12 @@ void setup() { Serial.print(F("Board : ")); Serial.println(ampel.board); +#ifdef AMPEL_CSV + csv_writer::initialize(ampel.sensorId); +#endif + #ifdef AMPEL_WIFI - WiFiConnect(ampel.sensorId); + wifi::connect(ampel.sensorId); Serial.print(F("WiFi - Status: ")); Serial.println(WiFi.status()); @@ -100,7 +104,7 @@ void setup() { ntp::initialize(); - if (MDNS.begin(ampel.sensorId.c_str())) { // Start the mDNS responder for SENSOR_ID.local + 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 { @@ -108,15 +112,11 @@ void setup() { } # ifdef AMPEL_MQTT - mqtt::initialize("CO2sensors/" + ampel.sensorId); + mqtt::initialize(ampel.sensorId); # endif } #endif -#ifdef AMPEL_CSV - csv_writer::initialize(); -#endif - #if defined(AMPEL_LORAWAN) && defined(ESP32) lorawan::initialize(); #endif diff --git a/ampel-firmware/co2_sensor.h b/ampel-firmware/co2_sensor.h index 275c6e755d84681230101df08bd5bd4060528bd1..318b9596ae50ce15476366c3481d45e238895291 100644 --- a/ampel-firmware/co2_sensor.h +++ b/ampel-firmware/co2_sensor.h @@ -22,7 +22,7 @@ namespace sensor { extern uint16_t co2; extern float temperature; extern float humidity; - extern char timestamp[23]; + extern char timestamp[]; void initialize(); bool processData(); diff --git a/ampel-firmware/csv_writer.cpp b/ampel-firmware/csv_writer.cpp index 199d9f39f38e99cd6e641a42e5690e07439522f0..7e30d44da9531d5a4022ce3aec2a3a5a9d51c987 100644 --- a/ampel-firmware/csv_writer.cpp +++ b/ampel-firmware/csv_writer.cpp @@ -78,13 +78,15 @@ namespace csv_writer { } #endif - const String filename = "/" + ampel.sensorId + ".csv"; + char filename[15]; // "/ESPxxxxxx.csv\0" int getAvailableSpace() { return getTotalSpace() - getUsedSpace(); } - void initialize() { + void initialize(const char *sensorId) { + snprintf(filename, sizeof(filename), "/%s.csv", sensorId); + Serial.print(F("Initializing FS...")); if (mountFS()) { Serial.println(F("done.")); @@ -187,7 +189,7 @@ namespace csv_writer { } csv_file.close(); } - Serial.println(F("################")); + Serial.println(F("######################")); } void formatFilesystem() { diff --git a/ampel-firmware/csv_writer.h b/ampel-firmware/csv_writer.h index ab4a87b2972e8b3ae597e637a6f309da0d829658..f85f34641aaf4f1915e773ad7c1675f846ecfa90 100644 --- a/ampel-firmware/csv_writer.h +++ b/ampel-firmware/csv_writer.h @@ -20,11 +20,11 @@ namespace config { extern uint16_t csv_interval; // [s] } namespace csv_writer { - extern char last_successful_write[23]; - void initialize(); - void logIfTimeHasCome(const char* timestamp, const int16_t &co2, const float &temperature, const float &humidity); + extern char last_successful_write[]; + void initialize(const char *sensorId); + void logIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temperature, const float &humidity); int getAvailableSpace(); - extern const String filename; + extern char filename[]; void setCSVinterval(int32_t csv_interval); void showCSVContent(); diff --git a/ampel-firmware/mqtt.cpp b/ampel-firmware/mqtt.cpp index fda75272a905792d62cb38f1e43b53c6bfd464b7..22a0e2def350a38b77336b954395ae5d55005a8e 100644 --- a/ampel-firmware/mqtt.cpp +++ b/ampel-firmware/mqtt.cpp @@ -23,13 +23,13 @@ namespace mqtt { unsigned long last_failed_at = 0; bool connected = false; - String publish_topic; + char publish_topic[21]; // e.g. "CO2sensors/ESPxxxxxx\0" const char *json_sensor_format; char last_successful_publish[23] = ""; - void initialize(String &topic) { + void initialize(const char *sensorId) { json_sensor_format = PSTR("{\"time\":\"%s\", \"co2\":%d, \"temp\":%.1f, \"rh\":%.1f}"); - publish_topic = topic; + snprintf(publish_topic, sizeof(publish_topic), "CO2sensors/%s", sensorId); #if defined(ESP8266) espClient.setInsecure(); // Sorry, we don't want to flash the sensors every 3 months. #endif @@ -49,7 +49,7 @@ namespace mqtt { char payload[75]; // Should be enough for json... 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)) { + if (mqttClient.publish(publish_topic, payload)) { Serial.println(F("OK")); ntp::getLocalTime(last_successful_publish); } else { @@ -96,7 +96,7 @@ namespace mqtt { led_effects::onBoardLEDOn(); // Wait for connection, at most 15s (default) - mqttClient.connect(publish_topic.c_str(), config::mqtt_user, config::mqtt_password); + mqttClient.connect(publish_topic, config::mqtt_user, config::mqtt_password); led_effects::onBoardLEDOff(); connected = mqttClient.connected(); @@ -104,7 +104,7 @@ namespace mqtt { 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()); + snprintf(control_topic, sizeof(control_topic), "%s/control", publish_topic); mqttClient.subscribe(control_topic); mqttClient.setCallback(controlSensorCallback); } @@ -153,11 +153,11 @@ namespace mqtt { // The sensor will send the info to "CO2sensors/ESP123456/info". void sendInfoAboutLocalNetwork() { char info_topic[60]; // Should be enough for "CO2sensors/ESP123456/info" - snprintf(info_topic, sizeof(info_topic), "%s/info", publish_topic.c_str()); + snprintf(info_topic, sizeof(info_topic), "%s/info", publish_topic); char payload[75]; // Should be enough for info json... const char *json_info_format = PSTR("{\"local_ip\":\"%s\", \"ssid\":\"%s\"}"); - snprintf(payload, sizeof(payload), json_info_format, WiFi.localIP().toString().c_str(), WiFi.SSID().c_str()); + snprintf(payload, sizeof(payload), json_info_format, wifi::local_ip, WIFI_SSID); mqttClient.publish(info_topic, payload); } diff --git a/ampel-firmware/mqtt.h b/ampel-firmware/mqtt.h index 9167c47dda720528f138ff048630d399e6656984..fb769d66ccddf6a3c390c2e976c52de5717564a6 100644 --- a/ampel-firmware/mqtt.h +++ b/ampel-firmware/mqtt.h @@ -13,7 +13,7 @@ namespace config { namespace mqtt { extern char last_successful_publish[]; extern bool connected; - void initialize(String &topic); + void initialize(const char *sensorId); void keepConnection(); void publishIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temp, const float &hum); diff --git a/ampel-firmware/util.cpp b/ampel-firmware/util.cpp index 8a593666ae0b4fa10fd9568ad29447a8680c0812..b1aeb86a4a828669a403537a4bc638b660db639c 100644 --- a/ampel-firmware/util.cpp +++ b/ampel-firmware/util.cpp @@ -13,20 +13,6 @@ const char *current_board = "ESP32"; const char *current_board = "UNKNOWN"; #endif -// Get last 3 bytes of ESP MAC (worldwide unique) -String macToID() { - uint8_t mac[6]; - WiFi.macAddress(mac); - String result; - for (int i = 3; i < 6; i++) { - if (mac[i] < 16) - result += '0'; - result += String(mac[i], HEX); - } - result.toLowerCase(); - return result; -} - //NOTE: ESP32 sometimes couldn't access the NTP server, and every loop would take +1000ms // ifdefs could be used to define functions specific to ESP32, e.g. with configTime namespace ntp { @@ -70,8 +56,18 @@ void Ampel::showFreeSpace() { Serial.println(F(" bytes.")); } +char sensorId[10]; // e.g "ESPxxxxxx\0" + +char* getSensorId() { + uint8_t mac[6]; + WiFi.macAddress(mac); + // Get last 3 bytes of ESP MAC (worldwide unique) + snprintf(sensorId, sizeof(sensorId), "ESP%02x%02x%02x", mac[3], mac[4], mac[5]); + return sensorId; +} + Ampel::Ampel() : - board(current_board), sensorId("ESP" + macToID()), max_loop_duration(0) { + board(current_board), sensorId(getSensorId()), 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", []() { diff --git a/ampel-firmware/util.h b/ampel-firmware/util.h index b99a5d52d6c137c92372223da317ae7e2bf2ad64..a2673331e320174cba430c03c0bbf31caa68da06 100644 --- a/ampel-firmware/util.h +++ b/ampel-firmware/util.h @@ -37,7 +37,7 @@ private: static void showFreeSpace(); public: const char *board; - const String sensorId; + const char *sensorId; uint32_t max_loop_duration; Ampel(); }; diff --git a/ampel-firmware/web_server.cpp b/ampel-firmware/web_server.cpp index ee20bf43cf57c5171a0b28a5915354c9cdf8dc48..531ca19cf7a3dd6534ccb93d553a7dd9561afc08 100644 --- a/ampel-firmware/web_server.cpp +++ b/ampel-firmware/web_server.cpp @@ -65,7 +65,7 @@ namespace web_server { #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" + "<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'>⬤</li>\n" // LED "</ul></div></div>\n" @@ -132,7 +132,7 @@ namespace web_server { #ifdef AMPEL_CSV "<script>\n" "document.body.style.cursor = 'default';\n" - "fetch('./%s',{credentials:'include'})\n" + "fetch('%s',{credentials:'include'})\n" ".then(response=>response.text())\n" ".then(csvText=>csvToTable(csvText))\n" ".then(htmlTable=>addLogTableToPage(htmlTable))\n" @@ -177,7 +177,7 @@ namespace web_server { http.on("/", handleWebServerRoot); http.on("/command", handleWebServerCommand); #ifdef AMPEL_CSV - http.on("/" + csv_writer::filename, handleWebServerCSV); + http.on(csv_writer::filename, handleWebServerCSV); //NOTE: csv_writer should have been initialized first. http.on("/delete_csv", HTTP_POST, handleDeleteCSV); #endif http.onNotFound(handlePageNotFound); @@ -212,10 +212,9 @@ 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, ampel.sensorId.c_str(), - WiFi.localIP().toString().c_str() + snprintf_P(content, sizeof(content), header_template, sensor::co2, ampel.sensorId, wifi::local_ip #ifdef AMPEL_CSV - , csv_writer::filename.c_str() + , csv_writer::filename #endif ); @@ -225,7 +224,7 @@ namespace web_server { http.send_P(200, PSTR("text/html"), content); // Body - snprintf_P(content, sizeof(content), body_template, ampel.sensorId.c_str(), sensor::co2, sensor::temperature, + 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, @@ -237,9 +236,8 @@ namespace web_server { 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.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); + config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId, ampel.sensorId, + wifi::local_ip, wifi::local_ip, get_free_heap_size(), ampel.max_loop_duration, ampel.board, dd, hh, mm, ss); Serial.print(F(" - Body size : ")); http.sendContent(content); @@ -248,7 +246,7 @@ namespace web_server { // Script snprintf_P(content, sizeof(content), script_template #ifdef AMPEL_CSV - , csv_writer::filename.c_str(), ampel.sensorId.c_str() + , csv_writer::filename, ampel.sensorId #endif ); @@ -264,7 +262,9 @@ namespace web_server { } if (FS_LIB.exists(csv_writer::filename)) { fs::File csv_file = FS_LIB.open(csv_writer::filename, "r"); - http.sendHeader("Content-Length", String(csv_file.size())); + 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 { @@ -276,9 +276,9 @@ namespace web_server { if (!shouldBeAllowed()) { return http.requestAuthentication(DIGEST_AUTH); } - Serial.print("Removing CSV file..."); + Serial.print(F("Removing CSV file...")); FS_LIB.remove(csv_writer::filename); - Serial.println(" Done!"); + Serial.println(F(" Done!")); http.sendHeader("Location", "/"); http.send(303); } diff --git a/ampel-firmware/wifi_util.cpp b/ampel-firmware/wifi_util.cpp index dc496293345f5b3f743e6ed369d5e792376514d9..a845140a8aa80e66c0279c7b74e486847aaf6355 100644 --- a/ampel-firmware/wifi_util.cpp +++ b/ampel-firmware/wifi_util.cpp @@ -12,34 +12,39 @@ namespace config { #endif } -// Initialize Wi-Fi -void WiFiConnect(const String &hostname) { - //NOTE: WiFi Multi could allow multiple SSID and passwords. - WiFi.persistent(false); // Don't write user & password to Flash. - WiFi.mode(WIFI_STA); // Set ESP to be a WiFi-client only +namespace wifi { + char local_ip[16]; // "255.255.255.255\0" + // Initialize Wi-Fi + void connect(const char *hostname) { + //NOTE: WiFi Multi could allow multiple SSID and passwords. + WiFi.persistent(false); // Don't write user & password to Flash. + WiFi.mode(WIFI_STA); // Set ESP to be a WiFi-client only #if defined(ESP8266) WiFi.hostname(hostname); #elif defined(ESP32) - WiFi.setHostname(hostname.c_str()); + WiFi.setHostname(hostname); #endif - Serial.print(F("WiFi - Connecting to ")); - Serial.println(config::wifi_ssid); - WiFi.begin(config::wifi_ssid, config::wifi_password); + Serial.print(F("WiFi - Connecting to ")); + Serial.println(config::wifi_ssid); + WiFi.begin(config::wifi_ssid, config::wifi_password); - // Wait for connection, at most wifi_timeout seconds - for (int i = 0; i <= config::wifi_timeout && (WiFi.status() != WL_CONNECTED); i++) { - led_effects::showRainbowWheel(); - Serial.print("."); - } - if (WiFi.status() == WL_CONNECTED) { - led_effects::showKITTWheel(color::green); - Serial.println(); - Serial.print(F("WiFi - Connected! IP address: ")); - Serial.println(WiFi.localIP()); - } else { - //TODO: Allow sensor to work as an Access Point, in order to define SSID & password? - led_effects::showKITTWheel(color::red); - Serial.println(F("Connection to WiFi failed")); + // Wait for connection, at most wifi_timeout seconds + for (int i = 0; i <= config::wifi_timeout && (WiFi.status() != WL_CONNECTED); i++) { + led_effects::showRainbowWheel(); + Serial.print("."); + } + if (WiFi.status() == WL_CONNECTED) { + led_effects::showKITTWheel(color::green); + Serial.println(); + Serial.print(F("WiFi - Connected! IP address: ")); + IPAddress address = WiFi.localIP(); + snprintf(local_ip, sizeof(local_ip), "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); + Serial.println(local_ip); + } else { + //TODO: Allow sensor to work as an Access Point, in order to define SSID & password? + led_effects::showKITTWheel(color::red); + Serial.println(F("Connection to WiFi failed")); + } } } diff --git a/ampel-firmware/wifi_util.h b/ampel-firmware/wifi_util.h index 7520ed78e070cf552500441f4828bb7d339392b8..1cfe3da134de83f735bad7b8d58cd825fbd5e5d1 100644 --- a/ampel-firmware/wifi_util.h +++ b/ampel-firmware/wifi_util.h @@ -5,6 +5,9 @@ #include "util.h" #include "led_effects.h" -void WiFiConnect(const String &hostname); +namespace wifi { + extern char local_ip[]; + void connect(const char *hostname); +} #endif