From 2a27a3a95a93445f6c3398738af8e9170a92503c Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 2 Dec 2025 15:01:39 -0500 Subject: [PATCH] add a power save mode listener and use it for the text sensor --- esphome/components/wifi/wifi_component.h | 15 +++++ .../wifi/wifi_component_esp8266.cpp | 10 ++- .../wifi/wifi_component_esp_idf.cpp | 10 ++- .../wifi/wifi_component_libretiny.cpp | 12 +++- .../components/wifi/wifi_component_pico_w.cpp | 10 ++- esphome/components/wifi_info/text_sensor.py | 12 ++-- .../wifi_info/wifi_info_text_sensor.cpp | 64 ++++++++----------- .../wifi_info/wifi_info_text_sensor.h | 23 ++++--- tests/components/wifi_info/common.yaml | 2 + .../components/wifi_info/test.esp32-idf.yaml | 5 -- 10 files changed, 97 insertions(+), 66 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 2148f2d4c7..be94e9462b 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -273,6 +273,16 @@ class WiFiConnectStateListener { virtual void on_wifi_connect_state(const std::string &ssid, const bssid_t &bssid) = 0; }; +/** Listener interface for WiFi power save mode changes. + * + * Components can implement this interface to receive power save mode updates + * without the overhead of std::function callbacks. + */ +class WiFiPowerSaveListener { + public: + virtual void on_wifi_power_save(WiFiPowerSaveMode mode) = 0; +}; + /// This component is responsible for managing the ESP WiFi interface. class WiFiComponent : public Component { public: @@ -419,6 +429,10 @@ class WiFiComponent : public Component { void add_connect_state_listener(WiFiConnectStateListener *listener) { this->connect_state_listeners_.push_back(listener); } + /** Add a listener for WiFi power save mode changes. + * Listener receives: WiFiPowerSaveMode + */ + void add_power_save_listener(WiFiPowerSaveListener *listener) { this->power_save_listeners_.push_back(listener); } #endif // USE_WIFI_LISTENERS #ifdef USE_WIFI_RUNTIME_POWER_SAVE @@ -581,6 +595,7 @@ class WiFiComponent : public Component { std::vector ip_state_listeners_; std::vector scan_results_listeners_; std::vector connect_state_listeners_; + std::vector power_save_listeners_; #endif // USE_WIFI_LISTENERS ESPPreferenceObject pref_; #ifdef USE_WIFI_FAST_CONNECT diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index c1c0dd470f..9fdae278c7 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -104,7 +104,15 @@ bool WiFiComponent::wifi_apply_power_save_() { break; } wifi_fpm_auto_sleep_set_in_null_mode(1); - return wifi_set_sleep_type(power_save); + bool success = wifi_set_sleep_type(power_save); + if (success) { +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } +#endif + } + return success; } #if LWIP_VERSION_MAJOR != 1 diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index e1f8108892..54fa40a173 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -280,7 +280,15 @@ bool WiFiComponent::wifi_apply_power_save_() { power_save = WIFI_PS_NONE; break; } - return esp_wifi_set_ps(power_save) == ESP_OK; + bool success = esp_wifi_set_ps(power_save) == ESP_OK; + if (success) { +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } +#endif + } + return success; } bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 0de7003899..a3a3c852b5 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -69,7 +69,17 @@ bool WiFiComponent::wifi_sta_pre_setup_() { delay(10); return true; } -bool WiFiComponent::wifi_apply_power_save_() { return WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); } +bool WiFiComponent::wifi_apply_power_save_() { + bool success = WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); + if (success) { +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } +#endif + } + return success; +} bool WiFiComponent::wifi_sta_ip_config_(const optional &manual_ip) { // enable STA if (!this->wifi_mode_(true, {})) diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index c7dc4120dd..5f4e6ffc69 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -54,7 +54,15 @@ bool WiFiComponent::wifi_apply_power_save_() { break; } int ret = cyw43_wifi_pm(&cyw43_state, pm); - return ret == 0; + bool success = ret == 0; + if (success) { +#ifdef USE_WIFI_LISTENERS + for (auto *listener : this->power_save_listeners_) { + listener->on_wifi_power_save(this->power_save_); + } +#endif + } + return success; } // TODO: The driver doesn't seem to have an API for this diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 16eff4caba..8a7f192367 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -32,7 +32,7 @@ DNSAddressWifiInfo = wifi_info_ns.class_( "DNSAddressWifiInfo", text_sensor.TextSensor, cg.Component ) PowerSaveModeWiFiInfo = wifi_info_ns.class_( - "PowerSaveModeWiFiInfo", text_sensor.TextSensor, cg.PollingComponent + "PowerSaveModeWiFiInfo", text_sensor.TextSensor, cg.Component ) CONFIG_SCHEMA = cv.Schema( @@ -62,12 +62,9 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema( DNSAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), - cv.Optional(CONF_POWER_SAVE_MODE): cv.All( - text_sensor.text_sensor_schema( - PowerSaveModeWiFiInfo, - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - ).extend(cv.polling_component_schema("60s")), - cv.only_on(["esp32"]), + cv.Optional(CONF_POWER_SAVE_MODE): text_sensor.text_sensor_schema( + PowerSaveModeWiFiInfo, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) @@ -79,6 +76,7 @@ _NETWORK_INFO_KEYS = { CONF_IP_ADDRESS, CONF_DNS_ADDRESS, CONF_SCAN_RESULTS, + CONF_POWER_SAVE_MODE, } diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 360cd979ea..a2d4c2c45d 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -6,29 +6,6 @@ namespace esphome::wifi_info { static const char *const TAG = "wifi_info"; -#ifdef USE_ESP32 -/// @brief Helper function to convert ESP32 WiFi power save mode to string -/// @param ps_mode WiFi power save mode from esp_wifi_get_ps() -/// @return const char pointer to the readable power save mode -/// -/// Maps ESP32 WiFi power save modes to user-friendly strings: -/// - WIFI_PS_NONE (no power saving) -> "NONE" -/// - WIFI_PS_MIN_MODEM (minimal modem sleep) -> "LIGHT" -/// - WIFI_PS_MAX_MODEM (maximum modem sleep) -> "HIGH" -static const char *wifi_ps_mode_to_string(wifi_ps_type_t ps_mode) { - switch (ps_mode) { - case WIFI_PS_NONE: - return "NONE"; - case WIFI_PS_MIN_MODEM: - return "LIGHT"; - case WIFI_PS_MAX_MODEM: - return "HIGH"; - default: - return "UNKNOWN"; - } -} -#endif // USE_ESP32 - #ifdef USE_WIFI_LISTENERS static constexpr size_t MAX_STATE_LENGTH = 255; @@ -123,6 +100,32 @@ void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::b this->publish_state(buf); } +/************************ + * PowerSaveModeWiFiInfo + ***********************/ + +void PowerSaveModeWiFiInfo::setup() { wifi::global_wifi_component->add_power_save_listener(this); } + +void PowerSaveModeWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WiFi Power Save Mode", this); } + +void PowerSaveModeWiFiInfo::on_wifi_power_save(wifi::WiFiPowerSaveMode mode) { + const char *mode_str; + switch (mode) { + case wifi::WIFI_POWER_SAVE_NONE: + mode_str = "NONE"; + break; + case wifi::WIFI_POWER_SAVE_LIGHT: + mode_str = "LIGHT"; + break; + case wifi::WIFI_POWER_SAVE_HIGH: + mode_str = "HIGH"; + break; + default: + mode_str = "UNKNOWN"; + break; + } + this->publish_state(mode_str); +} #endif /********************* @@ -131,20 +134,5 @@ void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::b void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "MAC Address", this); } -#ifdef USE_ESP32 -void PowerSaveModeWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "WiFi Power Save Mode", this); } - -void PowerSaveModeWiFiInfo::update() { - wifi_ps_type_t power_save_mode; - if (esp_wifi_get_ps(&power_save_mode) == ESP_OK) { - // Publish if the state has changed or if this is the first read - if (this->last_power_save_mode_ != power_save_mode || !this->has_state()) { - this->publish_state(wifi_ps_mode_to_string(power_save_mode)); - this->last_power_save_mode_ = power_save_mode; - } - } -} -#endif // USE_ESP32 - } // namespace esphome::wifi_info #endif diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 78afcad43f..5aad042491 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -66,6 +66,17 @@ class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pu // WiFiConnectStateListener interface void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; }; + +class PowerSaveModeWiFiInfo final : public Component, + public text_sensor::TextSensor, + public wifi::WiFiPowerSaveListener { + public: + void setup() override; + void dump_config() override; + + // WiFiPowerSaveListener interface + void on_wifi_power_save(wifi::WiFiPowerSaveMode mode) override; +}; #endif class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor { @@ -77,17 +88,5 @@ class MacAddressWifiInfo final : public Component, public text_sensor::TextSenso void dump_config() override; }; -#ifdef USE_ESP32 -class PowerSaveModeWiFiInfo : public PollingComponent, public text_sensor::TextSensor { - public: - void update() override; - float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } - void dump_config() override; - - protected: - wifi_ps_type_t last_power_save_mode_{}; -}; -#endif // USE_ESP32 - } // namespace esphome::wifi_info #endif diff --git a/tests/components/wifi_info/common.yaml b/tests/components/wifi_info/common.yaml index f87d381d0c..340eaca2a7 100644 --- a/tests/components/wifi_info/common.yaml +++ b/tests/components/wifi_info/common.yaml @@ -16,3 +16,5 @@ text_sensor: name: MAC Address dns_address: name: DNS ADdress + power_save_mode: + name: "WiFi Power Save Mode" diff --git a/tests/components/wifi_info/test.esp32-idf.yaml b/tests/components/wifi_info/test.esp32-idf.yaml index 648bdf47c9..b47e39c389 100644 --- a/tests/components/wifi_info/test.esp32-idf.yaml +++ b/tests/components/wifi_info/test.esp32-idf.yaml @@ -2,8 +2,3 @@ packages: i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml <<: !include common.yaml - -text_sensor: - - platform: wifi_info - power_save_mode: - name: "WiFi Power Save Mode"