diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index db203326679..7b31a22ed5b 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(); @@ -2129,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 8dfe5fa7afa..073341fe79a 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -670,8 +670,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 @@ -811,6 +814,12 @@ class WiFiComponent final : public Component { uint8_t num_ipv6_addresses_{0}; #endif /* USE_NETWORK_IPV6 */ bool error_from_callback_{false}; +#ifdef USE_ESP8266 + // ESP8266WiFiSTAState enum, defined in wifi_component_esp8266.cpp. + // Written from SDK system context (wifi_event_callback) — uint8_t writes + // are atomic on Xtensa LX106 so no synchronization is needed. + uint8_t sta_state_{0}; +#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 03800cc3a92..cb53d3ac1bd 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -44,11 +44,14 @@ 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.) +}; bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = wifi_get_opmode(); @@ -359,11 +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 - s_sta_connecting = true; - s_sta_connected = false; - s_sta_got_ip = false; - s_sta_connect_error = false; - s_sta_connect_not_found = false; + this->sta_state_ = static_cast(ESP8266WiFiSTAState::CONNECTING); ETS_UART_INTR_DISABLE(); ret = wifi_station_connect(); @@ -493,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 - s_sta_connected = true; + 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 @@ -506,16 +505,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; + 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))); - s_sta_connect_error = true; + global_wifi_component->sta_state_ = static_cast(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 +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)); - s_sta_got_ip = true; + 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; @@ -636,17 +633,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) + // Use cached state from wifi_event_callback() instead of calling + // 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 = static_cast(this->sta_state_); + if (state == ESP8266WiFiSTAState::CONNECTED) return WiFiSTAConnectStatus::CONNECTED; - if (status == STATION_NO_AP_FOUND) + if (state == ESP8266WiFiSTAState::ERROR_NOT_FOUND) return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - if (status == STATION_CONNECT_FAIL || status == STATION_WRONG_PASSWORD) + if (state == ESP8266WiFiSTAState::ERROR_FAILED) return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - if (status == STATION_CONNECTING) + if (state == ESP8266WiFiSTAState::CONNECTING || state == ESP8266WiFiSTAState::ASSOCIATED) return WiFiSTAConnectStatus::CONNECTING; return WiFiSTAConnectStatus::IDLE; } + bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA if (!this->wifi_mode_(true, {}))