diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index b395c77141..2ade015a25 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -179,12 +179,53 @@ void WiFiComponent::wifi_lazy_init_() { // nor re-register the default WiFi handlers. if (s_sta_netif == nullptr) s_sta_netif = esp_netif_create_default_wifi_sta(); + if (s_sta_netif == nullptr) { + // Allocation failed; leave wifi_initialized_ false so a later enable() retries. + ESP_LOGE(TAG, "esp_netif_create_default_wifi_sta failed"); + return; + } #ifdef USE_WIFI_AP if (s_ap_netif == nullptr) s_ap_netif = esp_netif_create_default_wifi_ap(); #endif // USE_WIFI_AP + // The WiFi driver was started (e.g. by ESP-NOW with the wifi component disabled at + // boot) before our STA netif existed. The default WIFI_EVENT_STA_START handler + // therefore ran with no netif and never called esp_wifi_register_if_rxcb() -- the + // only thing that points the driver's RX path at a netif (it sets + // s_wifi_netifs[WIFI_IF_STA]). A bare esp_netif_action_start() would stop the + // immediate crash (#17232) but leaves RX unbound, so the first association + // associates at L2 yet never receives DHCP replies and times out (#17239). Restart + // the driver now that the netif exists so STA_START re-runs the default handler and + // wires RX correctly. ESP-NOW survives the stop/start (its peer state persists). + // This also matches a self-retry: if esp_wifi_set_storage() below failed on a + // previous wifi_lazy_init_() it returned without setting wifi_initialized_, and + // esp_wifi_init() has since run, so esp_wifi_get_mode() now succeeds here too. + wifi_mode_t mode; + if (esp_wifi_get_mode(&mode) == ESP_OK) { + ESP_LOGD(TAG, "WiFi driver already started without STA netif; restarting to bind it"); + esp_err_t err = esp_wifi_stop(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_stop failed: %s", esp_err_to_name(err)); + } + // Re-apply RAM storage; the normal init path does this, but it is skipped on + // the self-retry case above, which would otherwise let the driver persist + // credentials to NVS for the rest of the boot. + err = esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_set_storage failed: %s", esp_err_to_name(err)); + } + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err)); + return; + } + s_wifi_started = true; + this->wifi_initialized_ = true; + return; + } + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); if (global_preferences->nvs_handle == 0) { ESP_LOGW(TAG, "starting wifi without nvs");