From f43a598a83f9dc318b4b2fa7503215ef4ac8f174 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 17:55:55 -1000 Subject: [PATCH 1/9] [wifi] Avoid redundant SDK calls in WiFi loop on ESP8266 Replace five separate boolean state variables in the ESP8266 WiFi implementation with a single enum state machine, matching the pattern already used by LibreTiny. This eliminates the per-loop call to wifi_station_get_connect_status() by reading cached state from the existing event callback instead. Also use the cached connected_ field (set unconditionally at the top of loop()) in the STA_CONNECTED branch instead of calling is_connected_() a second time. This applies to all platforms. --- esphome/components/wifi/wifi_component.cpp | 3 +- .../wifi/wifi_component_esp8266.cpp | 58 ++++++++++--------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index db20332667..a0489e93d3 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -784,7 +784,8 @@ void WiFiComponent::loop() { } case WIFI_COMPONENT_STATE_STA_CONNECTED: { - if (!this->is_connected_()) { + // Use cached connected_ set unconditionally at the top of loop() + if (!this->connected_) { ESP_LOGW(TAG, "Connection lost; reconnecting"); this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING; this->retry_connect(); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 03800cc3a9..f4e67bc322 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -44,11 +44,17 @@ namespace esphome::wifi { static const char *const TAG = "wifi_esp8266"; -static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +enum class ESP8266WiFiSTAState : uint8_t { + IDLE, // Not connecting + CONNECTING, // Connection in progress + ASSOCIATED, // Associated to AP, waiting for IP + CONNECTED, // Successfully connected with IP + ERROR_NOT_FOUND, // AP not found (probe failed) + ERROR_FAILED, // Connection failed (auth, timeout, etc.) +}; + +static ESP8266WiFiSTAState s_sta_state = + ESP8266WiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = wifi_get_opmode(); @@ -359,11 +365,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // Reset flags, do this _before_ wifi_station_connect as the callback method // may be called from wifi_station_connect - s_sta_connecting = true; - s_sta_connected = false; - s_sta_got_ip = false; - s_sta_connect_error = false; - s_sta_connect_not_found = false; + s_sta_state = ESP8266WiFiSTAState::CONNECTING; ETS_UART_INTR_DISABLE(); ret = wifi_station_connect(); @@ -493,7 +495,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, bssid_buf, it.channel); #endif - s_sta_connected = true; + s_sta_state = ESP8266WiFiSTAState::ASSOCIATED; #ifdef USE_WIFI_CONNECT_STATE_LISTENERS // Defer listener notification until state machine reaches STA_CONNECTED // This ensures wifi.connected condition returns true in listener automations @@ -506,16 +508,14 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { if (it.reason == REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, (const char *) it.ssid); - s_sta_connect_not_found = true; + s_sta_state = ESP8266WiFiSTAState::ERROR_NOT_FOUND; } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, (const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason))); - s_sta_connect_error = true; + s_sta_state = ESP8266WiFiSTAState::ERROR_FAILED; } - s_sta_connected = false; - s_sta_connecting = false; global_wifi_component->error_from_callback_ = true; #ifdef USE_WIFI_CONNECT_STATE_LISTENERS global_wifi_component->pending_.disconnect = true; @@ -541,7 +541,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { mask_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf), network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf)); - s_sta_got_ip = true; + s_sta_state = ESP8266WiFiSTAState::CONNECTED; #ifdef USE_WIFI_IP_STATE_LISTENERS // Defer listener callbacks to main loop - system context has limited stack global_wifi_component->pending_.got_ip = true; @@ -636,16 +636,22 @@ void WiFiComponent::wifi_pre_setup_() { } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const { - station_status_t status = wifi_station_get_connect_status(); - if (status == STATION_GOT_IP) - return WiFiSTAConnectStatus::CONNECTED; - if (status == STATION_NO_AP_FOUND) - return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - if (status == STATION_CONNECT_FAIL || status == STATION_WRONG_PASSWORD) - return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - if (status == STATION_CONNECTING) - return WiFiSTAConnectStatus::CONNECTING; - return WiFiSTAConnectStatus::IDLE; + // Use cached state from wifi_event_callback() instead of calling + // wifi_station_get_connect_status() which queries the SDK every time + switch (s_sta_state) { + case ESP8266WiFiSTAState::CONNECTED: + return WiFiSTAConnectStatus::CONNECTED; + case ESP8266WiFiSTAState::ERROR_NOT_FOUND: + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + case ESP8266WiFiSTAState::ERROR_FAILED: + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + case ESP8266WiFiSTAState::CONNECTING: + case ESP8266WiFiSTAState::ASSOCIATED: + return WiFiSTAConnectStatus::CONNECTING; + case ESP8266WiFiSTAState::IDLE: + default: + return WiFiSTAConnectStatus::IDLE; + } } bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA From 438553ccfeabb163e00e6c00dd4f18b97b4ef739 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:09:49 -1000 Subject: [PATCH 2/9] Fix clang-tidy NOLINT placement for s_sta_state Move the NOLINT comment to NOLINTNEXTLINE so clang-tidy sees it on the line with the variable declaration, not the continuation. --- esphome/components/wifi/wifi_component_esp8266.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index f4e67bc322..fc04d2559a 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -53,8 +53,8 @@ enum class ESP8266WiFiSTAState : uint8_t { ERROR_FAILED, // Connection failed (auth, timeout, etc.) }; -static ESP8266WiFiSTAState s_sta_state = - ESP8266WiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +static ESP8266WiFiSTAState s_sta_state = ESP8266WiFiSTAState::IDLE; bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = wifi_get_opmode(); From cee116829395a04184ae3de5c79d49d07d883d6a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:10:28 -1000 Subject: [PATCH 3/9] Use if-else instead of switch to avoid CSWTCH in rodata On ESP8266, GCC generates a lookup table in .rodata for switch statements, which lives in flash. Use if-else chain to keep the logic entirely in IRAM. --- .../wifi/wifi_component_esp8266.cpp | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index fc04d2559a..16beb91d88 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -637,21 +637,19 @@ void WiFiComponent::wifi_pre_setup_() { WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const { // Use cached state from wifi_event_callback() instead of calling - // wifi_station_get_connect_status() which queries the SDK every time - switch (s_sta_state) { - case ESP8266WiFiSTAState::CONNECTED: - return WiFiSTAConnectStatus::CONNECTED; - case ESP8266WiFiSTAState::ERROR_NOT_FOUND: - return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - case ESP8266WiFiSTAState::ERROR_FAILED: - return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - case ESP8266WiFiSTAState::CONNECTING: - case ESP8266WiFiSTAState::ASSOCIATED: - return WiFiSTAConnectStatus::CONNECTING; - case ESP8266WiFiSTAState::IDLE: - default: - return WiFiSTAConnectStatus::IDLE; - } + // wifi_station_get_connect_status() which queries the SDK every time. + // Use if-else instead of switch to avoid GCC generating a CSWTCH + // lookup table in .rodata (flash) on ESP8266. + auto state = s_sta_state; + if (state == ESP8266WiFiSTAState::CONNECTED) + return WiFiSTAConnectStatus::CONNECTED; + if (state == ESP8266WiFiSTAState::ERROR_NOT_FOUND) + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + if (state == ESP8266WiFiSTAState::ERROR_FAILED) + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + if (state == ESP8266WiFiSTAState::CONNECTING || state == ESP8266WiFiSTAState::ASSOCIATED) + return WiFiSTAConnectStatus::CONNECTING; + return WiFiSTAConnectStatus::IDLE; } bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA From d52d5bbda3765c96d9c27257a365592e96ffe052 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:16:41 -1000 Subject: [PATCH 4/9] Move ESP8266 WiFi STA state enum to member variable Replace the file-static s_sta_state with a member variable sta_state_ on WiFiComponent, consistent with error_from_callback_ and pending_ which are also written from the static callback via global_wifi_component. --- esphome/components/wifi/wifi_component.h | 14 +++++++++++ .../wifi/wifi_component_esp8266.cpp | 24 +++++-------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 8dfe5fa7af..351bda1498 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -398,6 +398,17 @@ class WiFiPowerSaveListener { virtual void on_wifi_power_save(WiFiPowerSaveMode mode) = 0; }; +#ifdef USE_ESP8266 +enum class ESP8266WiFiSTAState : uint8_t { + IDLE, // Not connecting + CONNECTING, // Connection in progress + ASSOCIATED, // Associated to AP, waiting for IP + CONNECTED, // Successfully connected with IP + ERROR_NOT_FOUND, // AP not found (probe failed) + ERROR_FAILED, // Connection failed (auth, timeout, etc.) +}; +#endif + /// This component is responsible for managing the ESP WiFi interface. class WiFiComponent final : public Component { public: @@ -811,6 +822,9 @@ class WiFiComponent final : public Component { uint8_t num_ipv6_addresses_{0}; #endif /* USE_NETWORK_IPV6 */ bool error_from_callback_{false}; +#ifdef USE_ESP8266 + ESP8266WiFiSTAState sta_state_{ESP8266WiFiSTAState::IDLE}; +#endif RetryHiddenMode retry_hidden_mode_{RetryHiddenMode::BLIND_RETRY}; RoamingState roaming_state_{RoamingState::IDLE}; bssid_t roaming_target_bssid_{}; // BSSID of the AP we're trying to roam to diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 16beb91d88..7bec557f7a 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -44,18 +44,6 @@ namespace esphome::wifi { static const char *const TAG = "wifi_esp8266"; -enum class ESP8266WiFiSTAState : uint8_t { - IDLE, // Not connecting - CONNECTING, // Connection in progress - ASSOCIATED, // Associated to AP, waiting for IP - CONNECTED, // Successfully connected with IP - ERROR_NOT_FOUND, // AP not found (probe failed) - ERROR_FAILED, // Connection failed (auth, timeout, etc.) -}; - -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -static ESP8266WiFiSTAState s_sta_state = ESP8266WiFiSTAState::IDLE; - bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = wifi_get_opmode(); bool current_sta = current_mode & 0b01; @@ -365,7 +353,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // Reset flags, do this _before_ wifi_station_connect as the callback method // may be called from wifi_station_connect - s_sta_state = ESP8266WiFiSTAState::CONNECTING; + this->sta_state_ = ESP8266WiFiSTAState::CONNECTING; ETS_UART_INTR_DISABLE(); ret = wifi_station_connect(); @@ -495,7 +483,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, bssid_buf, it.channel); #endif - s_sta_state = ESP8266WiFiSTAState::ASSOCIATED; + global_wifi_component->sta_state_ = ESP8266WiFiSTAState::ASSOCIATED; #ifdef USE_WIFI_CONNECT_STATE_LISTENERS // Defer listener notification until state machine reaches STA_CONNECTED // This ensures wifi.connected condition returns true in listener automations @@ -508,13 +496,13 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { if (it.reason == REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, (const char *) it.ssid); - s_sta_state = ESP8266WiFiSTAState::ERROR_NOT_FOUND; + global_wifi_component->sta_state_ = ESP8266WiFiSTAState::ERROR_NOT_FOUND; } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, (const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason))); - s_sta_state = ESP8266WiFiSTAState::ERROR_FAILED; + global_wifi_component->sta_state_ = ESP8266WiFiSTAState::ERROR_FAILED; } global_wifi_component->error_from_callback_ = true; #ifdef USE_WIFI_CONNECT_STATE_LISTENERS @@ -541,7 +529,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { mask_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf), network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf)); - s_sta_state = ESP8266WiFiSTAState::CONNECTED; + global_wifi_component->sta_state_ = ESP8266WiFiSTAState::CONNECTED; #ifdef USE_WIFI_IP_STATE_LISTENERS // Defer listener callbacks to main loop - system context has limited stack global_wifi_component->pending_.got_ip = true; @@ -640,7 +628,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const { // wifi_station_get_connect_status() which queries the SDK every time. // Use if-else instead of switch to avoid GCC generating a CSWTCH // lookup table in .rodata (flash) on ESP8266. - auto state = s_sta_state; + auto state = this->sta_state_; if (state == ESP8266WiFiSTAState::CONNECTED) return WiFiSTAConnectStatus::CONNECTED; if (state == ESP8266WiFiSTAState::ERROR_NOT_FOUND) From 96a23e5d3d422fd7eb8c930f2e3a67000b62b521 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:29:03 -1000 Subject: [PATCH 5/9] Inline is_connected_ and update_connected_state_ into header Move these small methods to the header so the compiler can inline them into loop(), eliminating two function call/return pairs from every loop iteration on all platforms. --- esphome/components/wifi/wifi_component.cpp | 5 ----- esphome/components/wifi/wifi_component.h | 7 +++++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index a0489e93d3..7b31a22ed5 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -2130,11 +2130,6 @@ void WiFiComponent::retry_connect() { } void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } -bool WiFiComponent::is_connected_() const { - return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && - this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_; -} -void WiFiComponent::update_connected_state_() { this->connected_ = this->is_connected_(); } void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; #if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 351bda1498..d3b23080c5 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -681,8 +681,11 @@ class WiFiComponent final : public Component { bool wifi_sta_connect_(const WiFiAP &ap); void wifi_pre_setup_(); WiFiSTAConnectStatus wifi_sta_connect_status_() const; - bool is_connected_() const; - void update_connected_state_(); + bool is_connected_() const { + return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && + this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_; + } + void update_connected_state_() { this->connected_ = this->is_connected_(); } bool wifi_scan_start_(bool passive); #ifdef USE_WIFI_AP From f19792df8c18bd147baab4e3c8010dd06be70e7f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:32:28 -1000 Subject: [PATCH 6/9] Revert wifi_sta_connect_status_ header inline attempt GCC doesn't inline it into loop() due to code size heuristics, so the #ifdef complexity isn't worth it. Keep it out-of-line. --- esphome/components/wifi/wifi_component_esp8266.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 7bec557f7a..cdb5a06a85 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -639,6 +639,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const { return WiFiSTAConnectStatus::CONNECTING; return WiFiSTAConnectStatus::IDLE; } + bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA if (!this->wifi_mode_(true, {})) From 7086cf3392a08de3e992630416f77209f4767647 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:33:15 -1000 Subject: [PATCH 7/9] Fix comment: if statements with early returns, not if-else --- esphome/components/wifi/wifi_component_esp8266.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index cdb5a06a85..8c2e0bfefe 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -626,8 +626,8 @@ void WiFiComponent::wifi_pre_setup_() { WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const { // Use cached state from wifi_event_callback() instead of calling // wifi_station_get_connect_status() which queries the SDK every time. - // Use if-else instead of switch to avoid GCC generating a CSWTCH - // lookup table in .rodata (flash) on ESP8266. + // Use if statements with early returns instead of switch to avoid GCC + // generating a CSWTCH lookup table in .rodata (flash) on ESP8266. auto state = this->sta_state_; if (state == ESP8266WiFiSTAState::CONNECTED) return WiFiSTAConnectStatus::CONNECTED; From 37e181a797fc93a68a44dea7b0cc8605b1690075 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:38:04 -1000 Subject: [PATCH 8/9] Move ESP8266WiFiSTAState enum to cpp, use uint8_t in header Keep the enum definition private to wifi_component_esp8266.cpp and store as uint8_t in the class to avoid leaking platform-specific types into the shared header. --- esphome/components/wifi/wifi_component.h | 13 +----------- .../wifi/wifi_component_esp8266.cpp | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d3b23080c5..100f3651b6 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -398,17 +398,6 @@ class WiFiPowerSaveListener { virtual void on_wifi_power_save(WiFiPowerSaveMode mode) = 0; }; -#ifdef USE_ESP8266 -enum class ESP8266WiFiSTAState : uint8_t { - IDLE, // Not connecting - CONNECTING, // Connection in progress - ASSOCIATED, // Associated to AP, waiting for IP - CONNECTED, // Successfully connected with IP - ERROR_NOT_FOUND, // AP not found (probe failed) - ERROR_FAILED, // Connection failed (auth, timeout, etc.) -}; -#endif - /// This component is responsible for managing the ESP WiFi interface. class WiFiComponent final : public Component { public: @@ -826,7 +815,7 @@ class WiFiComponent final : public Component { #endif /* USE_NETWORK_IPV6 */ bool error_from_callback_{false}; #ifdef USE_ESP8266 - ESP8266WiFiSTAState sta_state_{ESP8266WiFiSTAState::IDLE}; + uint8_t sta_state_{0}; // ESP8266WiFiSTAState, defined in wifi_component_esp8266.cpp #endif RetryHiddenMode retry_hidden_mode_{RetryHiddenMode::BLIND_RETRY}; RoamingState roaming_state_{RoamingState::IDLE}; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 8c2e0bfefe..cb53d3ac1b 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -44,6 +44,15 @@ namespace esphome::wifi { static const char *const TAG = "wifi_esp8266"; +enum class ESP8266WiFiSTAState : uint8_t { + IDLE, // Not connecting + CONNECTING, // Connection in progress + ASSOCIATED, // Associated to AP, waiting for IP + CONNECTED, // Successfully connected with IP + ERROR_NOT_FOUND, // AP not found (probe failed) + ERROR_FAILED, // Connection failed (auth, timeout, etc.) +}; + bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = wifi_get_opmode(); bool current_sta = current_mode & 0b01; @@ -353,7 +362,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // Reset flags, do this _before_ wifi_station_connect as the callback method // may be called from wifi_station_connect - this->sta_state_ = ESP8266WiFiSTAState::CONNECTING; + this->sta_state_ = static_cast(ESP8266WiFiSTAState::CONNECTING); ETS_UART_INTR_DISABLE(); ret = wifi_station_connect(); @@ -483,7 +492,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, bssid_buf, it.channel); #endif - global_wifi_component->sta_state_ = ESP8266WiFiSTAState::ASSOCIATED; + global_wifi_component->sta_state_ = static_cast(ESP8266WiFiSTAState::ASSOCIATED); #ifdef USE_WIFI_CONNECT_STATE_LISTENERS // Defer listener notification until state machine reaches STA_CONNECTED // This ensures wifi.connected condition returns true in listener automations @@ -496,13 +505,13 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { if (it.reason == REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, (const char *) it.ssid); - global_wifi_component->sta_state_ = ESP8266WiFiSTAState::ERROR_NOT_FOUND; + global_wifi_component->sta_state_ = static_cast(ESP8266WiFiSTAState::ERROR_NOT_FOUND); } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, (const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason))); - global_wifi_component->sta_state_ = ESP8266WiFiSTAState::ERROR_FAILED; + global_wifi_component->sta_state_ = static_cast(ESP8266WiFiSTAState::ERROR_FAILED); } global_wifi_component->error_from_callback_ = true; #ifdef USE_WIFI_CONNECT_STATE_LISTENERS @@ -529,7 +538,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { mask_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf), network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf)); - global_wifi_component->sta_state_ = ESP8266WiFiSTAState::CONNECTED; + global_wifi_component->sta_state_ = static_cast(ESP8266WiFiSTAState::CONNECTED); #ifdef USE_WIFI_IP_STATE_LISTENERS // Defer listener callbacks to main loop - system context has limited stack global_wifi_component->pending_.got_ip = true; @@ -628,7 +637,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const { // wifi_station_get_connect_status() which queries the SDK every time. // Use if statements with early returns instead of switch to avoid GCC // generating a CSWTCH lookup table in .rodata (flash) on ESP8266. - auto state = this->sta_state_; + auto state = static_cast(this->sta_state_); if (state == ESP8266WiFiSTAState::CONNECTED) return WiFiSTAConnectStatus::CONNECTED; if (state == ESP8266WiFiSTAState::ERROR_NOT_FOUND) From fc0625c0f74f6c82be6367327047b74c4a2bf031 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 18:46:37 -1000 Subject: [PATCH 9/9] [wifi] Move LibreTiny WiFi STA state to member variable Replace the file-static s_sta_state with the shared sta_state_ member variable on WiFiComponent, matching the ESP8266 change. All accesses are in member functions so no global_wifi_component indirection is needed. --- esphome/components/wifi/wifi_component.h | 4 +-- .../wifi/wifi_component_libretiny.cpp | 25 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 100f3651b6..ad126af227 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -814,8 +814,8 @@ class WiFiComponent final : public Component { uint8_t num_ipv6_addresses_{0}; #endif /* USE_NETWORK_IPV6 */ bool error_from_callback_{false}; -#ifdef USE_ESP8266 - uint8_t sta_state_{0}; // ESP8266WiFiSTAState, defined in wifi_component_esp8266.cpp +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) + uint8_t sta_state_{0}; // Platform-specific enum, defined in platform cpp file #endif RetryHiddenMode retry_hidden_mode_{RetryHiddenMode::BLIND_RETRY}; RoamingState roaming_state_{RoamingState::IDLE}; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index b049a0413c..9565ffa747 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -97,8 +97,6 @@ enum class LTWiFiSTAState : uint8_t { ERROR_FAILED, // Connection failed (auth, timeout, etc.) }; -static LTWiFiSTAState s_sta_state = LTWiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - // Count of ignored disconnect events during connection - too many indicates real failure static uint8_t s_ignored_disconnect_count = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) // Threshold for ignored disconnect events before treating as connection failure @@ -223,7 +221,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); // Reset state machine and disconnect counter before connecting - s_sta_state = LTWiFiSTAState::CONNECTING; + this->sta_state_ = static_cast(LTWiFiSTAState::CONNECTING); s_ignored_disconnect_count = 0; WiFiStatus status = WiFi.begin(ap.ssid_.c_str(), ap.password_.empty() ? NULL : ap.password_.c_str(), @@ -459,7 +457,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { } case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "STA stop"); - s_sta_state = LTWiFiSTAState::IDLE; + this->sta_state_ = static_cast(LTWiFiSTAState::IDLE); break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { @@ -479,7 +477,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { // For static IP configurations, GOT_IP event may not fire, so set connected state here #ifdef USE_WIFI_MANUAL_IP if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { - s_sta_state = LTWiFiSTAState::CONNECTED; + this->sta_state_ = static_cast(LTWiFiSTAState::CONNECTED); #ifdef USE_WIFI_IP_STATE_LISTENERS this->notify_ip_state_listeners_(); #endif @@ -501,12 +499,13 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { // Only ignore benign reasons - real failures like NO_AP_FOUND should still be processed. // However, if we get too many of these events (IGNORED_DISCONNECT_THRESHOLD), treat it // as a real connection failure to avoid waiting the full timeout for a failing connection. - if (it.ssid_len == 0 && s_sta_state == LTWiFiSTAState::CONNECTING && it.reason != WIFI_REASON_NO_AP_FOUND) { + if (it.ssid_len == 0 && this->sta_state_ == static_cast(LTWiFiSTAState::CONNECTING) && + it.reason != WIFI_REASON_NO_AP_FOUND) { s_ignored_disconnect_count++; if (s_ignored_disconnect_count >= IGNORED_DISCONNECT_THRESHOLD) { ESP_LOGW(TAG, "Too many disconnect events (%u) while connecting, treating as failure (reason=%s)", s_ignored_disconnect_count, get_disconnect_reason_str(it.reason)); - s_sta_state = LTWiFiSTAState::ERROR_FAILED; + this->sta_state_ = static_cast(LTWiFiSTAState::ERROR_FAILED); WiFi.disconnect(); this->error_from_callback_ = true; // Don't break - fall through to notify listeners @@ -520,13 +519,13 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, (const char *) it.ssid); - s_sta_state = LTWiFiSTAState::ERROR_NOT_FOUND; + this->sta_state_ = static_cast(LTWiFiSTAState::ERROR_NOT_FOUND); } else { char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(it.bssid, bssid_s); ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, (const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason)); - s_sta_state = LTWiFiSTAState::ERROR_FAILED; + this->sta_state_ = static_cast(LTWiFiSTAState::ERROR_FAILED); } uint8_t reason = it.reason; @@ -551,7 +550,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting"); WiFi.disconnect(); this->error_from_callback_ = true; - s_sta_state = LTWiFiSTAState::ERROR_FAILED; + this->sta_state_ = static_cast(LTWiFiSTAState::ERROR_FAILED); } break; } @@ -559,7 +558,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGV(TAG, "static_ip=%s gateway=%s", network::IPAddress(WiFi.localIP()).str_to(ip_buf), network::IPAddress(WiFi.gatewayIP()).str_to(gw_buf)); - s_sta_state = LTWiFiSTAState::CONNECTED; + this->sta_state_ = static_cast(LTWiFiSTAState::CONNECTED); #ifdef USE_WIFI_IP_STATE_LISTENERS this->notify_ip_state_listeners_(); #endif @@ -637,7 +636,7 @@ void WiFiComponent::wifi_pre_setup_() { WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const { // Use state machine instead of querying WiFi.status() directly // State is updated in main loop from queued events, ensuring thread safety - switch (s_sta_state) { + switch (static_cast(this->sta_state_)) { case LTWiFiSTAState::CONNECTED: return WiFiSTAConnectStatus::CONNECTED; case LTWiFiSTAState::ERROR_NOT_FOUND: @@ -758,7 +757,7 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; bool WiFiComponent::wifi_disconnect_() { // Reset state first so disconnect events aren't ignored // and wifi_sta_connect_status_() returns IDLE instead of CONNECTING - s_sta_state = LTWiFiSTAState::IDLE; + this->sta_state_ = static_cast(LTWiFiSTAState::IDLE); return WiFi.disconnect(); }