From d931d285deb5f2c551c1a567d243e19b64d98305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 00:05:28 +0100 Subject: [PATCH 1/8] Polish WiFiDriver --- main/kernel/drivers/WiFiDriver.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/kernel/drivers/WiFiDriver.hpp b/main/kernel/drivers/WiFiDriver.hpp index 958617c4..990ea834 100644 --- a/main/kernel/drivers/WiFiDriver.hpp +++ b/main/kernel/drivers/WiFiDriver.hpp @@ -191,7 +191,7 @@ class WiFiDriver { } } - inline void runLoop() { + void runLoop() { int clients = 0; bool connected = false; std::optional> connectingSince; From 474e8d19db73a2142c6a7613e9c294a061ceb67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 00:08:36 +0100 Subject: [PATCH 2/8] Fix component definition --- main/CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 327f0764..7f74d9de 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,11 @@ FILE(GLOB_RECURSE app_sources main.cpp **/*.cpp) +idf_component_register( + SRCS ${app_sources} + INCLUDE_DIRS "." + EMBED_TXTFILES "${CMAKE_SOURCE_DIR}/partitions.csv" +) + if(NOT DEFINED WOKWI) set(WOKWI "$ENV{WOKWI}") endif() @@ -11,12 +17,6 @@ if(WOKWI) component_compile_definitions(WOKWI) endif() -idf_component_register( - SRCS ${app_sources} - INCLUDE_DIRS "." - EMBED_TXTFILES "${CMAKE_SOURCE_DIR}/partitions.csv" -) - component_compile_definitions("${UD_GEN}") component_compile_definitions(FARMHUB_REPORT_MEMORY) From e72f4e562b69077e0cd59b996ce0020d090ff11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 01:24:39 +0100 Subject: [PATCH 3/8] Reset color on debug console upon boot --- main/kernel/Log.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main/kernel/Log.hpp b/main/kernel/Log.hpp index dff195d7..6f9ba769 100644 --- a/main/kernel/Log.hpp +++ b/main/kernel/Log.hpp @@ -46,6 +46,11 @@ void convertFromJson(JsonVariantConst src, Level& dst) { } static void initLogging() { +#ifdef FARMHUB_DEBUG + // Reset ANSI colors + printf("\033[0m"); +#endif + const char* logTags[] = { "farmhub", "farmhub:mdns", From b6eca9e6c3c3111ea8d547ca9345005711ce88d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 01:25:25 +0100 Subject: [PATCH 4/8] Show nice colors for verbose and debug logs --- main/kernel/Console.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main/kernel/Console.hpp b/main/kernel/Console.hpp index c68c2c42..34f5d4e6 100644 --- a/main/kernel/Console.hpp +++ b/main/kernel/Console.hpp @@ -84,6 +84,12 @@ class ConsoleProvider { case Level::Info: count += printf(FARMHUB_LOG_COLOR(FARMHUB_LOG_COLOR_GREEN)); break; + case Level::Debug: + count += printf(FARMHUB_LOG_COLOR(FARMHUB_LOG_COLOR_CYAN)); + break; + case Level::Verbose: + count += printf(FARMHUB_LOG_COLOR(FARMHUB_LOG_COLOR_BLUE)); + break; default: break; } From 68c3d357c4d00a6552b20a1f5fbc988b5f329fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 01:36:40 +0100 Subject: [PATCH 5/8] Use bumblebee as the location --- data-templates/device-config-wokwi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-templates/device-config-wokwi.json b/data-templates/device-config-wokwi.json index 9dbb5250..3bd929db 100644 --- a/data-templates/device-config-wokwi.json +++ b/data-templates/device-config-wokwi.json @@ -1,7 +1,7 @@ { "id": "wokwi", "instance": "wokwi", - "location": "local", + "location": "bumblebee", "peripherals": [ ], "sleepWhenIdle": true From 85079f048390df53b9c9906e461bb213f617c379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 01:38:08 +0100 Subject: [PATCH 6/8] Simplify MQTT connection logic We now handle all the events on the run loop. We also only initialize the MQTT client once, and then just start and stop it. --- main/kernel/BootClock.hpp | 2 +- main/kernel/mqtt/MqttDriver.hpp | 403 +++++++++++++++++--------------- 2 files changed, 211 insertions(+), 194 deletions(-) diff --git a/main/kernel/BootClock.hpp b/main/kernel/BootClock.hpp index dfe13b57..56d5ce37 100644 --- a/main/kernel/BootClock.hpp +++ b/main/kernel/BootClock.hpp @@ -22,7 +22,7 @@ struct boot_clock { return time_point(duration(esp_timer_get_time())); } - static time_point boot_time() noexcept { + static time_point zero() noexcept { return time_point(duration(0)); } }; diff --git a/main/kernel/mqtt/MqttDriver.hpp b/main/kernel/mqtt/MqttDriver.hpp index 66c0c7a2..3b3b4ecc 100644 --- a/main/kernel/mqtt/MqttDriver.hpp +++ b/main/kernel/mqtt/MqttDriver.hpp @@ -66,7 +66,7 @@ class MqttDriver { const QoS qos; const TaskHandle_t waitingTask; const LogPublish log; - const milliseconds extendAlert; + const milliseconds extendKeepAlive; static const uint32_t PUBLISH_SUCCESS = 1; static const uint32_t PUBLISH_FAILED = 2; @@ -88,6 +88,10 @@ class MqttDriver { const bool success; }; + struct Connected { + const bool sessionPresent; + }; + struct Disconnected { }; public: @@ -123,6 +127,11 @@ class MqttDriver { , incomingQueue("mqtt-incoming", config.queueSize.get()) { Task::run("mqtt", 5120, [this](Task& task) { + configMqttClient(mqttConfig); + client = esp_mqtt_client_init(&mqttConfig); + + ESP_ERROR_CHECK(esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, handleMqttEventCallback, this)); + runEventLoop(task); }); Task::loop("mqtt:incoming", 4096, [this](Task& task) { @@ -132,12 +141,74 @@ class MqttDriver { }); } + void configMqttClient(esp_mqtt_client_config_t& config) { + if (configHostname.isEmpty()) { +#ifdef WOKWI + hostname = "host.wokwi.internal"; + port = 1883; +#else + MdnsRecord mqttServer; + while (!mdns.lookupService("mqtt", "tcp", mqttServer, trustMdnsCache)) { + LOGTE("mqtt", "Failed to lookup MQTT server from mDNS"); + trustMdnsCache = false; + Task::delay(5s); + } + trustMdnsCache = true; + hostname = mqttServer.ip == IPAddress() + ? mqttServer.hostname + : mqttServer.ip.toString(); + port = mqttServer.port; +#endif + } else { + hostname = configHostname; + port = configPort; + } + + config = { + .broker { + .address { + .hostname = hostname.c_str(), + .port = port, + }, + }, + .credentials { + .client_id = clientId.c_str(), + }, + .network { + .timeout_ms = duration_cast(10s).count(), + }, + }; + + LOGTD("mqtt", "server: %s:%ld, client ID is '%s'", + config.broker.address.hostname, + config.broker.address.port, + config.credentials.client_id); + + if (!configServerCert.isEmpty()) { + config.broker.address.transport = MQTT_TRANSPORT_OVER_SSL; + config.broker.verification.certificate = configServerCert.c_str(); + LOGTV("mqtt", "Server cert:\n%s", + config.broker.verification.certificate); + + if (!configClientCert.isEmpty() && !configClientKey.isEmpty()) { + config.credentials.authentication = { + .certificate = configClientCert.c_str(), + .key = configClientKey.c_str(), + }; + LOGTV("mqtt", "Client cert:\n%s", + config.credentials.authentication.certificate); + } + } else { + config.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; + } + } + shared_ptr forRoot(const String& topic) { return make_shared(*this, topic); } private: - PublishStatus publish(const String& topic, const JsonDocument& json, Retention retain, QoS qos, ticks timeout = ticks::zero(), LogPublish log = LogPublish::Log, milliseconds extendAlert = MQTT_ALERT_AFTER_OUTGOING) { + PublishStatus publish(const String& topic, const JsonDocument& json, Retention retain, QoS qos, ticks timeout = ticks::zero(), LogPublish log = LogPublish::Log, milliseconds extendKeepAlive = MQTT_KEEP_ALIVE_AFTER_OUTGOING) { if (log == LogPublish::Log) { #ifdef DUMP_MQTT String serializedJson; @@ -159,17 +230,17 @@ class MqttDriver { String payload; serializeJson(json, payload); return executeAndAwait(timeout, [&](TaskHandle_t waitingTask) { - return eventQueue.offerIn(MQTT_QUEUE_TIMEOUT, OutgoingMessage { topic, payload, retain, qos, waitingTask, log, extendAlert }); + return eventQueue.offerIn(MQTT_QUEUE_TIMEOUT, OutgoingMessage { topic, payload, retain, qos, waitingTask, log, extendKeepAlive }); }); } - PublishStatus clear(const String& topic, Retention retain, QoS qos, ticks timeout = ticks::zero(), milliseconds extendAlert = MQTT_ALERT_AFTER_OUTGOING) { + PublishStatus clear(const String& topic, Retention retain, QoS qos, ticks timeout = ticks::zero(), milliseconds extendKeepAlive = MQTT_KEEP_ALIVE_AFTER_OUTGOING) { LOGTD("mqtt", "Clearing topic '%s' (qos = %d, timeout = %lld ms)", topic.c_str(), static_cast(qos), duration_cast(timeout).count()); return executeAndAwait(timeout, [&](TaskHandle_t waitingTask) { - return eventQueue.offerIn(MQTT_QUEUE_TIMEOUT, OutgoingMessage { topic, "", retain, qos, waitingTask, LogPublish::Log, extendAlert }); + return eventQueue.offerIn(MQTT_QUEUE_TIMEOUT, OutgoingMessage { topic, "", retain, qos, waitingTask, LogPublish::Log, extendKeepAlive }); }); } @@ -218,196 +289,163 @@ class MqttDriver { } void runEventLoop(Task& task) { - // TODO Extract constant - time_point alertUntil = system_clock::now() + 5s; + // How long we need to stay connected after a connection is made + // - to await incoming messages + // - to publish outgoing QoS = 0 messages + auto keepAliveAfterConnection = milliseconds::zero(); + auto keepAliveUntil = boot_clock::zero(); + + // We are not yet connected + bool connected = false; + bool connecting = false; + + // The first session is always clean + bool startCleanSession = true; while (true) { - milliseconds timeout; - if (powerSaveMode) { - timeout = duration_cast(alertUntil - system_clock::now()); - if (timeout > milliseconds::zero()) { - LOGTV("mqtt", "Alert for another %lld ms, checking for incoming messages", - timeout.count()); - ensureConnected(); - timeout = std::min(timeout, MQTT_LOOP_INTERVAL); - } else if (!pendingMessages.empty()) { - LOGTV("mqtt", "Alert expired, but there are pending messages, staying connected"); - ensureConnected(); - timeout = MQTT_LOOP_INTERVAL; - } else if (powerSaveMode) { - LOGTV("mqtt", "Not alert anymore, disconnecting"); + auto now = boot_clock::now(); + bool shouldBeConencted = + // We stay connected all the time if we are not in power save mode + !powerSaveMode + // We stay connected if there are pending messages (timeouts will cause them to be removed) + || !pendingMessages.empty() + // We stay connected if recent subscriptions or published QoS = 0 messages force us to keep alive + || keepAliveUntil > now + || keepAliveAfterConnection > milliseconds::zero(); + + if (shouldBeConencted) { + if (!connected) { + // TODO Retry connecting and re-lookup MQTT server from mDNS if necessary + if (!connecting) { + connecting = true; + connect(startCleanSession); + } + } + } else { + if (connected || connecting) { + connected = false; + connecting = false; + mqttReady.clear(); disconnect(); - timeout = MQTT_MAX_TIMEOUT_POWER_SAVE; } + } + + milliseconds timeout; + if (powerSaveMode && connected && keepAliveUntil > now) { + auto keepAlivePeriod = duration_cast(keepAliveUntil - now); + LOGTV("mqtt", "Keeping alive for %lld ms", + keepAlivePeriod.count()); + timeout = std::min(keepAlivePeriod, MQTT_LOOP_INTERVAL); } else { - LOGTV("mqtt", "Power save mode not enabled, staying connected"); - ensureConnected(); + // LOGTV("mqtt", "Waiting for event for %lld ms", + // duration_cast(MQTT_LOOP_INTERVAL).count()); timeout = MQTT_LOOP_INTERVAL; } - // LOGTV("mqtt", "Waiting for event for %lld ms", duration_cast(timeout).count()); eventQueue.drainIn(duration_cast(timeout), [&](const auto& event) { + milliseconds extendKeepAlive = milliseconds::zero(); std::visit( [&](auto&& arg) { using T = std::decay_t; - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { + LOGTV("mqtt", "Processing connected event, session present: %d", + arg.sessionPresent); + connected = true; + connecting = false; + + // This is what we have been waiting for, let's keep alive + keepAliveUntil = boot_clock::now() + keepAliveAfterConnection; + keepAliveAfterConnection = milliseconds::zero(); + + // Next connection can start with a persistent session + startCleanSession = false; + if (!arg.sessionPresent) { + // Re-subscribe to existing subscriptions + // because we got a clean session + for (auto& subscription : subscriptions) { + registerSubscriptionWithMqtt(subscription); + } + } + } else if constexpr (std::is_same_v) { + LOGTV("mqtt", "Processing disconnected event"); + connected = false; + connecting = false; + + // If we lost connection while keeping alive, let's make + // sure we keep alive for a while after reconnection + auto now = boot_clock::now(); + if (keepAliveUntil > now) { + keepAliveAfterConnection = duration_cast(keepAliveUntil - now); + } + keepAliveUntil = boot_clock::zero(); + + // Clear pending messages and notify waiting tasks + for (auto& message : pendingMessages) { + notifyWaitingTask(message.waitingTask, false); + } + pendingMessages.clear(); + } else if constexpr (std::is_same_v) { + LOGTV("mqtt", "Processing message published"); + pendingMessages.remove_if([this, arg](const auto& pendingMessage) { + if (pendingMessage.messageId == arg.messageId) { + notifyWaitingTask(pendingMessage.waitingTask, arg.success); + return true; + } else { + return false; + } + }); + } else if constexpr (std::is_same_v) { LOGTV("mqtt", "Processing outgoing message to %s", arg.topic.c_str()); - ensureConnected(); processOutgoingMessage(arg); - alertUntil = std::max(alertUntil, system_clock::now() + arg.extendAlert); + extendKeepAlive = std::max(extendKeepAlive, arg.extendKeepAlive); } else if constexpr (std::is_same_v) { LOGTV("mqtt", "Processing subscription"); - ensureConnected(); - processSubscription(arg); - alertUntil = std::max(alertUntil, system_clock::now() + MQTT_ALERT_AFTER_OUTGOING); - } else if constexpr (std::is_same_v) { - LOGTV("mqtt", "Processing message published"); - processMessagePublished(arg); - } else if constexpr (std::is_same_v) { - LOGTV("mqtt", "Processing disconnected event"); - processDisconnected(); + subscriptions.push_back(arg); + if (connected) { + // If we are connected, we need to subscribe immediately. + registerSubscriptionWithMqtt(arg); + } else { + // If we are not connected, we need to rely on the next + // clean session to make the subscription. + startCleanSession = true; + } + extendKeepAlive = std::max(extendKeepAlive, MQTT_KEEP_ALIVE_AFTER_OUTGOING); } }, event); + if (extendKeepAlive > milliseconds::zero()) { + if (connected) { + keepAliveUntil = std::max(keepAliveUntil, boot_clock::now() + extendKeepAlive); + } else { + keepAliveAfterConnection = std::max(keepAliveAfterConnection, extendKeepAlive); + } + } }); } } - void ensureConnected() { - while (!connectIfNecessary()) { - // Do exponential backoff - Task::delay(MQTT_DISCONNECTED_CHECK_INTERVAL); - } - } - - void disconnect() { - if (client != nullptr) { - LOGTD("mqtt", "Disconnecting from MQTT server"); - mqttReady.clear(); - ESP_ERROR_CHECK(esp_mqtt_client_disconnect(client)); - stopMqttClient(); - wifiConnection.reset(); - } - } - - void stopMqttClient() { - if (client != nullptr) { - ESP_ERROR_CHECK(esp_mqtt_client_stop(client)); - destroyMqttClient(); - } - } - - void destroyMqttClient() { - if (client != nullptr) { - ESP_ERROR_CHECK(esp_mqtt_client_destroy(client)); - client = nullptr; - } - } - - bool connectIfNecessary() { + void connect(bool startCleanSession) { + // TODO Do not block on WiFi connection if (!wifiConnection.has_value()) { LOGTV("mqtt", "Connecting to WiFi..."); wifiConnection.emplace(wifi); LOGTV("mqtt", "Connected to WiFi"); } - if (!mqttReady.isSet()) { - if (client == nullptr) { - LOGTD("mqtt", "Connecting to MQTT server"); - String hostname; - uint32_t port; - if (configHostname.isEmpty()) { -#ifdef WOKWI - hostname = "host.wokwi.internal"; - port = 1883; -#else - MdnsRecord mqttServer; - if (!mdns.lookupService("mqtt", "tcp", mqttServer, trustMdnsCache)) { - LOGTE("mqtt", "Failed to lookup MQTT server"); - return false; - } - hostname = mqttServer.ip == IPAddress() - ? mqttServer.hostname - : mqttServer.ip.toString(); - port = mqttServer.port; -#endif - } else { - hostname = configHostname; - port = configPort; - } - - esp_mqtt_client_config_t config = { - .broker { - .address { - .hostname = hostname.c_str(), - .port = port, - }, - }, - .credentials { - .client_id = clientId.c_str(), - }, - .network { - .timeout_ms = duration_cast(10s).count(), - }, - }; - - LOGTD("mqtt", "server: %s:%ld, client ID is '%s'", - config.broker.address.hostname, - config.broker.address.port, - config.credentials.client_id); - - if (!configServerCert.isEmpty()) { - config.broker.address.transport = MQTT_TRANSPORT_OVER_SSL; - config.broker.verification.certificate = configServerCert.c_str(); - LOGTV("mqtt", "Server cert:\n%s", - config.broker.verification.certificate); - - if (!configClientCert.isEmpty() && !configClientKey.isEmpty()) { - config.credentials.authentication = { - .certificate = configClientCert.c_str(), - .key = configClientKey.c_str(), - }; - LOGTV("mqtt", "Client cert:\n%s", - config.credentials.authentication.certificate); - } - } else { - config.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; - } - - client = esp_mqtt_client_init(&config); - - ESP_ERROR_CHECK(esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, handleMqttEventCallback, this)); - - esp_err_t err = esp_mqtt_client_start(client); - if (err != ESP_OK) { - LOGTE("mqtt", "Connection failed, error = 0x%x: %s", - err, esp_err_to_name(err)); - trustMdnsCache = false; - destroyMqttClient(); - return false; - } else { - trustMdnsCache = true; - } - } else { - // TODO Reconnection probably doesn't work like this? - LOGTD("mqtt", "Reconnecting to MQTT server"); - ESP_ERROR_CHECK(esp_mqtt_client_reconnect(client)); - } - if (!mqttReady.awaitSet(MQTT_CONNECTION_TIMEOUT)) { - LOGTD("mqtt", "Connecting to MQTT server timed out"); - stopMqttClient(); - return false; - } - - // Re-subscribe to existing subscriptions - for (auto& subscription : subscriptions) { - registerSubscriptionWithMqtt(subscription); - } + LOGTD("mqtt", "Starting MQTT client, clean session: %d", + startCleanSession); + mqttConfig.session.disable_clean_session = !startCleanSession; + esp_mqtt_set_config(client, &mqttConfig); + ESP_ERROR_CHECK(esp_mqtt_client_start(client)); + } - LOGTD("mqtt", "Connected"); - } - return true; + void disconnect() { + mqttReady.clear(); + LOGTD("mqtt", "Disconnecting from MQTT server"); + ESP_ERROR_CHECK(esp_mqtt_client_disconnect(client)); + ESP_ERROR_CHECK(esp_mqtt_client_stop(client)); + wifiConnection.reset(); } static void handleMqttEventCallback(void* userData, esp_event_base_t eventBase, int32_t eventId, void* eventData) { @@ -421,12 +459,13 @@ class MqttDriver { void handleMqttEvent(int eventId, esp_mqtt_event_handle_t event) { switch (eventId) { case MQTT_EVENT_BEFORE_CONNECT: { - LOGTV("mqtt", "Connecting to MQTT server"); + LOGTV("mqtt", "Before connecting to MQTT server"); break; } case MQTT_EVENT_CONNECTED: { LOGTV("mqtt", "Connected to MQTT server"); mqttReady.set(); + eventQueue.offerIn(MQTT_QUEUE_TIMEOUT, Connected { (bool) event->session_present }); break; } case MQTT_EVENT_DISCONNECTED: { @@ -530,31 +569,6 @@ class MqttDriver { } } - void processMessagePublished(const MessagePublished& event) { - pendingMessages.remove_if([this, event](const auto& pendingMessage) { - if (pendingMessage.messageId == event.messageId) { - notifyWaitingTask(pendingMessage.waitingTask, event.success); - return true; - } else { - return false; - } - }); - } - - void processSubscription(const Subscription& subscription) { - if (registerSubscriptionWithMqtt(subscription)) { - subscriptions.push_back(subscription); - } - } - - void processDisconnected() { - mqttReady.clear(); - for (auto& message : pendingMessages) { - notifyWaitingTask(message.waitingTask, false); - } - pendingMessages.clear(); - } - void processIncomingMessage(const IncomingMessage& message) { const String& topic = message.topic; const String& payload = message.payload; @@ -631,9 +645,12 @@ class MqttDriver { const bool powerSaveMode; StateSource& mqttReady; - esp_mqtt_client_handle_t client = nullptr; + String hostname; + uint32_t port; + esp_mqtt_client_config_t mqttConfig; + esp_mqtt_client_handle_t client; - Queue> eventQueue; + Queue> eventQueue; Queue incomingQueue; // TODO Use a map instead std::list subscriptions; @@ -649,7 +666,7 @@ class MqttDriver { static constexpr milliseconds MQTT_LOOP_INTERVAL = 1s; static constexpr milliseconds MQTT_DISCONNECTED_CHECK_INTERVAL = 5s; static constexpr milliseconds MQTT_QUEUE_TIMEOUT = 1s; - static constexpr milliseconds MQTT_ALERT_AFTER_OUTGOING = 1500ms; + static constexpr milliseconds MQTT_KEEP_ALIVE_AFTER_OUTGOING = 1500ms; static constexpr milliseconds MQTT_MAX_TIMEOUT_POWER_SAVE = 1h; friend class MqttRoot; From 3e1262bdda7dc3eda5148e2223947e4102fa02c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 02:03:33 +0100 Subject: [PATCH 7/8] Initialize WiFi only once Do not de-init WiFi every time we disconnect. Instead, stay initialized throughout the devices lifetime. --- main/kernel/drivers/WiFiDriver.hpp | 44 +++++++++--------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/main/kernel/drivers/WiFiDriver.hpp b/main/kernel/drivers/WiFiDriver.hpp index 990ea834..6e9144e3 100644 --- a/main/kernel/drivers/WiFiDriver.hpp +++ b/main/kernel/drivers/WiFiDriver.hpp @@ -43,6 +43,10 @@ class WiFiDriver { ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &WiFiDriver::onEvent, this)); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &WiFiDriver::onEvent, this)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_FLASH)); + Task::run("wifi-driver", 4096, [this](Task&) { runLoop(); }); @@ -202,8 +206,8 @@ class WiFiDriver { if (networkRequested.isSet() && !configPortalRunning.isSet()) { esp_err_t err = esp_wifi_connect(); if (err != ESP_OK) { - LOGTD("wifi", "Failed to start connecting: %s", esp_err_to_name(err)); - ensureWifiDeinitialized(); + LOGTD("wifi", "Failed to start connecting: %s, stopping", esp_err_to_name(err)); + ensureWifiStopped(); } } break; @@ -239,7 +243,7 @@ class WiFiDriver { LOGTI("wifi", "Connection timed out, retrying"); networkConnecting.clear(); - ensureWifiDeinitialized(); + ensureWifiStopped(); } LOGTV("wifi", "Connecting for first client"); connectingSince = boot_clock::now(); @@ -256,7 +260,6 @@ class WiFiDriver { void connect() { networkConnecting.set(); - ensureWifiInitialized(); #ifdef WOKWI LOGTD("wifi", "Skipping provisioning on Wokwi"); @@ -288,35 +291,12 @@ class WiFiDriver { void disconnect() { if (powerSaveMode) { LOGTV("wifi", "No more clients, shutting down radio to conserve power"); - ensureWifiDeinitialized(); + ensureWifiStopped(); } else { LOGTV("wifi", "No more clients, but staying online because not saving power"); } } - void ensureWifiInitialized() { - if (!wifiInitialized) { - LOGTD("wifi", "Initializing WiFi"); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_FLASH)); - wifiUpSince = boot_clock::now(); - wifiInitialized = true; - } - } - - void ensureWifiDeinitialized() { - if (wifiInitialized) { - ensureWifiStopped(); - auto currentUptime = currentWifiUptime(); - wifiUptimeBefore += currentUptime; - wifiUpSince.reset(); - LOGTD("wifi", "De-initializing WiFi (spent %lld ms initialized)", currentUptime.count()); - ESP_ERROR_CHECK(esp_wifi_deinit()); - wifiInitialized = false; - } - } - milliseconds currentWifiUptime() { if (!wifiUpSince.has_value()) { return milliseconds::zero(); @@ -325,7 +305,7 @@ class WiFiDriver { } void ensureWifiStationStarted(wifi_config_t& config) { - ensureWifiInitialized(); + wifiUpSince = boot_clock::now(); if (!stationStarted.isSet()) { if (powerSaveMode) { auto listenInterval = 50; @@ -351,6 +331,10 @@ class WiFiDriver { LOGTD("wifi", "Stopping"); ESP_ERROR_CHECK(esp_wifi_stop()); } + auto currentUptime = currentWifiUptime(); + wifiUptimeBefore += currentUptime; + wifiUpSince.reset(); + LOGTD("wifi", "Stopping WiFi (was up %lld ms)", currentUptime.count()); } void ensureWifiDisconnected() { @@ -367,8 +351,6 @@ class WiFiDriver { } void startProvisioning() { - ensureWifiInitialized(); - // Initialize provisioning manager wifi_prov_mgr_config_t config = { .scheme = wifi_prov_scheme_softap, From 526065f2e186dfa0527d3c4bed93a64bc196a2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 15 Dec 2024 02:10:51 +0100 Subject: [PATCH 8/8] Add some peripherals to Wokwi test --- data-templates/device-config-wokwi.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data-templates/device-config-wokwi.json b/data-templates/device-config-wokwi.json index 3bd929db..d65802bd 100644 --- a/data-templates/device-config-wokwi.json +++ b/data-templates/device-config-wokwi.json @@ -3,6 +3,18 @@ "instance": "wokwi", "location": "bumblebee", "peripherals": [ + { + "name": "flow-control-a", + "type": "flow-control", + "params": { + "valve": { + "motor": "a" + }, + "flow-meter": { + "pin": "A1" + } + } + } ], "sleepWhenIdle": true }