mirror of
https://github.com/esphome/esphome.git
synced 2026-09-12 07:47:33 +00:00
feat(wifi): Unit B+ — defer esp_wifi_init() to lazy-init
WiFi already had enable_on_boot + enable()/disable()/is_disabled() lifecycle, but enable_on_boot:false didn't actually save any memory. wifi_pre_setup_() called esp_wifi_init() and esp_netif_create_default_wifi_sta() unconditionally during setup(), which allocates ~15-30KB of DMA-capable internal SRAM (RX/TX buffers, driver state, PHY init). The flag only skipped esp_wifi_start() in the followup branch — the driver was already resident, just not associated. This commit splits wifi_pre_setup_() into two parts: - wifi_pre_setup_() (light, kept in setup() always): MAC setup, event group creation, WIFI_EVENT/IP_EVENT handler registration. No DMA allocation. - wifi_lazy_init_() (heavy, NEW): esp_netif_create_default_wifi_sta()/_ap(), esp_wifi_init(), esp_wifi_set_storage(). The DMA-allocating calls. Guarded by wifi_initialized_ flag for idempotency. setup() calls wifi_lazy_init_() only when enable_on_boot_=true. The else branch sets WIFI_COMPONENT_STATE_DISABLED without any heavy init — the dormant interface costs zero DMA-capable memory. enable() calls wifi_lazy_init_() before start(), so a runtime enable after boot-time disable does the heavy init on demand. Idempotent — subsequent enable/disable cycles don't re-allocate. disable() is unchanged — it stops wifi but doesn't deinit. A future "release_on_disable" variant could call esp_wifi_deinit() to actually free the memory at runtime, but that requires coordinating with consumers holding wifi-bound sockets and is out of scope here. ESP-IDF only. Other platforms (Arduino on ESP32, ESP8266) keep the existing behavior — their wifi_pre_setup_() lives in different per-platform files. Field-tested on ESP32-S3 with W5500 SPI ethernet + audio + bluetooth_proxy. Before Unit B+: ~14KB free internal during peak load, crash on W5500 SPI DMA buffer allocation. After Unit B+: ~32KB free internal, Min Free 78KB in some test configurations — sufficient headroom for the other DMA consumers (I2S audio, BT controller) to operate.
This commit is contained in:
@@ -632,11 +632,14 @@ void WiFiComponent::setup() {
|
||||
#endif
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
#ifdef USE_ESP32
|
||||
this->wifi_lazy_init_();
|
||||
#endif
|
||||
this->start();
|
||||
} else {
|
||||
#ifdef USE_ESP32
|
||||
esp_netif_init();
|
||||
#endif
|
||||
// Note: esp_netif_init() used to be called here as a fallback for non-IDF builds.
|
||||
// It is now centralized in NetworkComponent::setup(), which runs at AFTER_BLUETOOTH
|
||||
// (300) ahead of WiFi (250). No action needed.
|
||||
this->state_ = WIFI_COMPONENT_STATE_DISABLED;
|
||||
}
|
||||
}
|
||||
@@ -1278,6 +1281,11 @@ void WiFiComponent::enable() {
|
||||
|
||||
ESP_LOGD(TAG, "Enabling");
|
||||
this->state_ = WIFI_COMPONENT_STATE_OFF;
|
||||
#ifdef USE_ESP32
|
||||
// Idempotent — only allocates DMA buffers + netifs on the first call. After this,
|
||||
// start() can safely run.
|
||||
this->wifi_lazy_init_();
|
||||
#endif
|
||||
this->start();
|
||||
}
|
||||
|
||||
|
||||
@@ -694,6 +694,12 @@ class WiFiComponent final : public Component {
|
||||
bool wifi_apply_hostname_();
|
||||
bool wifi_sta_connect_(const WiFiAP &ap);
|
||||
void wifi_pre_setup_();
|
||||
#ifdef USE_ESP32
|
||||
// ESP-IDF only: defers esp_wifi_init() + netif creation (which allocate ~15-30KB of
|
||||
// DMA-capable internal SRAM) until wifi actually needs to come up. Idempotent.
|
||||
// Called from setup() only when enable_on_boot_=true, and from enable() on first use.
|
||||
void wifi_lazy_init_();
|
||||
#endif
|
||||
WiFiSTAConnectStatus wifi_sta_connect_status_() const;
|
||||
bool is_connected_() const {
|
||||
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
|
||||
@@ -889,6 +895,12 @@ class WiFiComponent final : public Component {
|
||||
bool rrm_{false};
|
||||
#endif
|
||||
bool enable_on_boot_{true};
|
||||
#ifdef USE_ESP32
|
||||
// Tracks whether esp_wifi_init() + netif creation has happened. Allows enable()
|
||||
// to be called at runtime without re-allocating, and ensures the heavy init is
|
||||
// skipped entirely when enable_on_boot_ is false until first enable().
|
||||
bool wifi_initialized_{false};
|
||||
#endif
|
||||
bool got_ipv4_address_{false};
|
||||
bool keep_scan_results_{false};
|
||||
bool has_completed_scan_after_captive_portal_start_{
|
||||
|
||||
@@ -163,6 +163,16 @@ void WiFiComponent::wifi_pre_setup_() {
|
||||
ESP_LOGE(TAG, "esp_event_handler_instance_register failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
// NOTE: netif creation + esp_wifi_init() used to live here. They allocate ~15-30KB of
|
||||
// DMA-capable internal SRAM, which competes with W5500 SPI DMA and I2S DMA on
|
||||
// memory-tight devices. They are now deferred to wifi_lazy_init_(), called from
|
||||
// setup() when enable_on_boot_ is true, or from enable() on first runtime enable.
|
||||
// This makes enable_on_boot:false genuinely skip the wifi DMA allocation.
|
||||
}
|
||||
|
||||
void WiFiComponent::wifi_lazy_init_() {
|
||||
if (this->wifi_initialized_)
|
||||
return;
|
||||
|
||||
s_sta_netif = esp_netif_create_default_wifi_sta();
|
||||
|
||||
@@ -175,7 +185,7 @@ void WiFiComponent::wifi_pre_setup_() {
|
||||
ESP_LOGW(TAG, "starting wifi without nvs");
|
||||
cfg.nvs_enable = false;
|
||||
}
|
||||
err = esp_wifi_init(&cfg);
|
||||
esp_err_t err = esp_wifi_init(&cfg);
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
@@ -185,6 +195,7 @@ void WiFiComponent::wifi_pre_setup_() {
|
||||
ESP_LOGE(TAG, "esp_wifi_set_storage failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
this->wifi_initialized_ = true;
|
||||
}
|
||||
|
||||
bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
|
||||
|
||||
Reference in New Issue
Block a user