From 59f728488eedda6976c27a041a27b41c5d772456 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:58:30 +1300 Subject: [PATCH 1/2] [media_player.speaker] Dynamic auto load (#11084) Co-authored-by: J. Nick Koston --- esphome/components/psram/__init__.py | 2 ++ .../speaker/media_player/__init__.py | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index 6b85e7f720..8e4f9d7eac 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -63,6 +63,8 @@ SPIRAM_SPEEDS = { def supported() -> bool: + if not CORE.is_esp32: + return False variant = get_esp32_variant() return variant in SPIRAM_MODES diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 69ea0a53c6..7537a61e4e 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -6,7 +6,7 @@ from pathlib import Path from esphome import automation, external_files import esphome.codegen as cg -from esphome.components import audio, esp32, media_player, speaker +from esphome.components import audio, esp32, media_player, psram, speaker import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, @@ -26,10 +26,21 @@ from esphome.const import ( from esphome.core import CORE, HexInt from esphome.core.entity_helpers import inherit_property_from from esphome.external_files import download_content +from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) -AUTO_LOAD = ["audio", "psram"] + +def AUTO_LOAD(config: ConfigType) -> list[str]: + load = ["audio"] + if ( + not config + or config.get(CONF_TASK_STACK_IN_PSRAM) + or config.get(CONF_CODEC_SUPPORT_ENABLED) + ): + return load + ["psram"] + return load + CODEOWNERS = ["@kahrendt", "@synesthesiam"] DOMAIN = "media_player" @@ -279,7 +290,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BUFFER_SIZE, default=1000000): cv.int_range( min=4000, max=4000000 ), - cv.Optional(CONF_CODEC_SUPPORT_ENABLED, default=True): cv.boolean, + cv.Optional( + CONF_CODEC_SUPPORT_ENABLED, default=psram.supported() + ): cv.boolean, cv.Optional(CONF_FILES): cv.ensure_list(MEDIA_FILE_TYPE_SCHEMA), cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean, cv.Optional(CONF_VOLUME_INCREMENT, default=0.05): cv.percentage, From 6f3a9966983806b52c0730995c01d478336b2264 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Oct 2025 20:12:34 -1000 Subject: [PATCH 2/2] [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):