From 6f3a9966983806b52c0730995c01d478336b2264 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Oct 2025 20:12:34 -1000 Subject: [PATCH] [wifi] Free scan results memory after successful connection --- esphome/components/wifi/__init__.py | 21 +++++++++++++++++++++ esphome/components/wifi/wifi_component.cpp | 6 ++++++ esphome/components/wifi/wifi_component.h | 2 ++ esphome/components/wifi_info/text_sensor.py | 4 +++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a784123006..286e66a06b 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -468,6 +468,27 @@ async def wifi_disable_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg) +_FLAGS = {"keep_scan_results": False} + + +def request_wifi_scan_results(): + """Request that WiFi scan results be kept in memory after connection. + + Components that need access to scan results after WiFi is connected should + call this function during their code generation. This prevents the WiFi component from + freeing scan result memory after successful connection. + """ + _FLAGS["keep_scan_results"] = True + + +@coroutine_with_priority(CoroPriority.FINAL) +async def final_step(): + """Final code generation step to configure scan result retention.""" + if _FLAGS["keep_scan_results"]: + wifi_var = cg.MockObj(id="global_wifi_component", base="wifi::WiFiComponent *") + cg.add(wifi_var.set_keep_scan_results(True)) + + @automation.register_action( "wifi.configure", WiFiConfigureAction, diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 71ee4271ba..0a30b82aaf 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -716,6 +716,12 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; + // Free scan results memory unless a component needs them + if (!this->keep_scan_results_) { + this->scan_result_.clear(); + this->scan_result_.shrink_to_fit(); + } + if (this->fast_connect_) { this->save_fast_connect_settings_(); } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ee62ec1a69..c0b63e1858 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -316,6 +316,7 @@ class WiFiComponent : public Component { int8_t wifi_rssi(); void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } + void set_keep_scan_results(bool keep_scan_results) { this->keep_scan_results_ = keep_scan_results; } Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }; Trigger<> *get_disconnect_trigger() const { return this->disconnect_trigger_; }; @@ -424,6 +425,7 @@ class WiFiComponent : public Component { #endif bool enable_on_boot_; bool got_ipv4_address_{false}; + bool keep_scan_results_{false}; // Pointers at the end (naturally aligned) Trigger<> *connect_trigger_{new Trigger<>()}; diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 4ceb73a695..ac1c1bee05 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import text_sensor +from esphome.components import text_sensor, wifi import esphome.config_validation as cv from esphome.const import ( CONF_BSSID, @@ -77,6 +77,8 @@ async def to_code(config): await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) await setup_conf(config, CONF_MAC_ADDRESS) + if CONF_SCAN_RESULTS in config: + wifi.request_wifi_scan_results() await setup_conf(config, CONF_SCAN_RESULTS) await setup_conf(config, CONF_DNS_ADDRESS) if conf := config.get(CONF_IP_ADDRESS):