From 4a8422164172de8c769ef5a96b90683204c99fcf Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 21 Oct 2025 16:48:50 -0400 Subject: [PATCH] Add a WiFi power mode debug text sensor --- esphome/components/debug/debug_component.cpp | 1 + esphome/components/debug/debug_component.h | 24 ++++++++++- esphome/components/debug/debug_esp32.cpp | 40 ++++++++++++++++++ esphome/components/debug/debug_esp8266.cpp | 41 +++++++++++++++++++ esphome/components/debug/debug_libretiny.cpp | 22 ++++++++++ esphome/components/debug/debug_rp2040.cpp | 41 ++++++++++++++++++- esphome/components/debug/text_sensor.py | 13 ++++++ tests/components/debug/test.bk72xx-ard.yaml | 9 ++++ tests/components/debug/test.esp32-ard.yaml | 9 ++++ tests/components/debug/test.esp32-idf.yaml | 9 ++++ tests/components/debug/test.esp32-s2-idf.yaml | 9 ++++ tests/components/debug/test.esp8266-ard.yaml | 9 ++++ tests/components/debug/test.ln882x-ard.yaml | 9 ++++ .../components/debug/test.nrf52-xiao-ble.yaml | 9 ++++ tests/components/debug/test.rp2040-ard.yaml | 9 ++++ tests/components/fan/test.esp8266-ard.yaml | 9 ++++ 16 files changed, 261 insertions(+), 2 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index f54bf82eae0..790635e6c7b 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -18,6 +18,7 @@ void DebugComponent::dump_config() { ESP_LOGCONFIG(TAG, "Debug component:"); #ifdef USE_TEXT_SENSOR LOG_TEXT_SENSOR(" ", "Device info", this->device_info_); + LOG_TEXT_SENSOR(" ", "WiFi Power Save Mode", this->wifi_power_save_); #endif // USE_TEXT_SENSOR #ifdef USE_SENSOR LOG_SENSOR(" ", "Free space on heap", this->free_sensor_); diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index 96306f7cdfe..5b3c34a1b6e 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -10,7 +10,18 @@ #endif #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" -#endif +#ifdef USE_WIFI +#ifdef USE_ESP32 +#include +#elif defined(USE_ESP8266) +extern "C" { +#include +} +#elif defined(USE_RP2040) +#include +#endif // USE_ESP32 / USE_ESP8266 / USE_RP2040 +#endif // USE_WIFI +#endif // USE_TEXT_SENSOR namespace esphome { namespace debug { @@ -25,6 +36,7 @@ class DebugComponent : public PollingComponent { #ifdef USE_TEXT_SENSOR void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; } void set_reset_reason_sensor(text_sensor::TextSensor *reset_reason) { reset_reason_ = reset_reason; } + void set_wifi_power_save_sensor(text_sensor::TextSensor *wifi_power_save) { wifi_power_save_ = wifi_power_save; } #endif // USE_TEXT_SENSOR #ifdef USE_SENSOR void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } @@ -79,6 +91,16 @@ class DebugComponent : public PollingComponent { #ifdef USE_TEXT_SENSOR text_sensor::TextSensor *device_info_{nullptr}; text_sensor::TextSensor *reset_reason_{nullptr}; + text_sensor::TextSensor *wifi_power_save_{nullptr}; +#if defined(USE_WIFI) && defined(USE_ESP32) + wifi_ps_type_t last_wifi_ps_mode_{}; +#elif defined(USE_WIFI) && defined(USE_ESP8266) + sleep_type_t last_wifi_sleep_type_{}; +#elif defined(USE_WIFI) && defined(USE_RP2040) + uint32_t last_wifi_pm_{CYW43_PERFORMANCE_PM}; +#elif defined(USE_WIFI) && defined(USE_LIBRETINY) + bool last_wifi_sleep_{false}; +#endif #endif // USE_TEXT_SENSOR std::string get_reset_reason_(); diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 1c3dc3699b8..3d84b3a235b 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -11,6 +11,10 @@ #include #include +#ifdef USE_WIFI +#include +#endif + #ifdef USE_ARDUINO #include #endif @@ -44,6 +48,29 @@ static const char *const RESET_REASONS[] = { static const char *const REBOOT_KEY = "reboot_source"; static const size_t REBOOT_MAX_LEN = 24; +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) +/// @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_TEXT_SENSOR && USE_WIFI + // on shutdown, store the source of the reboot request void DebugComponent::on_shutdown() { auto *component = App.get_current_component(); @@ -234,6 +261,19 @@ void DebugComponent::update_platform_() { this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); } #endif + +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) + if (this->wifi_power_save_ != nullptr) { + 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_wifi_ps_mode_ != power_save_mode || !this->wifi_power_save_->has_state()) { + this->wifi_power_save_->publish_state(wifi_ps_mode_to_string(power_save_mode)); + this->last_wifi_ps_mode_ = power_save_mode; + } + } + } +#endif } } // namespace debug diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp index 3395d9db121..c8429014107 100644 --- a/esphome/components/debug/debug_esp8266.cpp +++ b/esphome/components/debug/debug_esp8266.cpp @@ -3,11 +3,41 @@ #include "esphome/core/log.h" #include +#ifdef USE_WIFI +extern "C" { +#include +} +#endif + namespace esphome { namespace debug { static const char *const TAG = "debug"; +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) +/// @brief Helper function to convert ESP8266 WiFi sleep type to string +/// @param sleep_type WiFi sleep type from wifi_get_sleep_type() +/// @return const char pointer to the readable sleep type +/// +/// Maps ESP8266 WiFi sleep types to user-friendly strings: +/// - NONE_SLEEP_T (no sleep) -> "NONE" +/// - LIGHT_SLEEP_T (light sleep) -> "LIGHT" +/// - MODEM_SLEEP_T (modem sleep) -> "HIGH" +/// - RF_CAL_SLEEP_T (RF calibration sleep) -> "UNKNOWN" (special mode, rarely used) +static const char *wifi_sleep_type_to_string(sleep_type_t sleep_type) { + switch (sleep_type) { + case NONE_SLEEP_T: + return "NONE"; + case LIGHT_SLEEP_T: + return "LIGHT"; + case MODEM_SLEEP_T: + return "HIGH"; + default: + return "UNKNOWN"; + } +} +#endif // USE_TEXT_SENSOR && USE_WIFI + std::string DebugComponent::get_reset_reason_() { #if !defined(CLANG_TIDY) return ESP.getResetReason().c_str(); @@ -87,6 +117,17 @@ void DebugComponent::update_platform_() { #endif #endif + +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) + if (this->wifi_power_save_ != nullptr) { + sleep_type_t sleep_type = wifi_get_sleep_type(); + // Publish if the state has changed or if this is the first read + if (this->last_wifi_sleep_type_ != sleep_type || !this->wifi_power_save_->has_state()) { + this->wifi_power_save_->publish_state(wifi_sleep_type_to_string(sleep_type)); + this->last_wifi_sleep_type_ = sleep_type; + } + } +#endif } } // namespace debug diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index b5e2a5b3103..a09daa8ea12 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -7,6 +7,17 @@ namespace debug { static const char *const TAG = "debug"; +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) +/// @brief Helper function to convert LibreTiny WiFi sleep state to string +/// @param sleep_enabled WiFi sleep enabled state from WiFi.getSleep() +/// @return const char pointer to the readable sleep state +/// +/// LibreTiny WiFi sleep is a boolean on/off setting: +/// - true (sleep enabled) -> "ON" +/// - false (sleep disabled) -> "OFF" +static const char *wifi_sleep_to_string(bool sleep_enabled) { return sleep_enabled ? "ON" : "OFF"; } +#endif // USE_TEXT_SENSOR && USE_WIFI + std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_name(lt_get_reboot_reason()); } uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); } @@ -37,6 +48,17 @@ void DebugComponent::update_platform_() { this->block_sensor_->publish_state(lt_heap_get_max_alloc()); } #endif + +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) + if (this->wifi_power_save_ != nullptr) { + bool sleep_enabled = WiFi.getSleep(); + // Publish if the state has changed or if this is the first read + if (this->last_wifi_sleep_ != sleep_enabled || !this->wifi_power_save_->has_state()) { + this->wifi_power_save_->publish_state(wifi_sleep_to_string(sleep_enabled)); + this->last_wifi_sleep_ = sleep_enabled; + } + } +#endif } } // namespace debug diff --git a/esphome/components/debug/debug_rp2040.cpp b/esphome/components/debug/debug_rp2040.cpp index 497547e30d7..8892d36323b 100644 --- a/esphome/components/debug/debug_rp2040.cpp +++ b/esphome/components/debug/debug_rp2040.cpp @@ -2,11 +2,39 @@ #ifdef USE_RP2040 #include "esphome/core/log.h" #include + +#ifdef USE_WIFI +#include +#endif + namespace esphome { namespace debug { static const char *const TAG = "debug"; +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) +/// @brief Helper function to convert RP2040 CYW43 WiFi power mode to string +/// @param pm WiFi power mode from cyw43_state.pm +/// @return const char pointer to the readable power mode +/// +/// Maps RP2040 CYW43 WiFi power modes to user-friendly strings: +/// - CYW43_PERFORMANCE_PM (no power saving) -> "NONE" +/// - CYW43_DEFAULT_PM (default power saving) -> "LIGHT" +/// - CYW43_AGGRESSIVE_PM (aggressive power saving) -> "HIGH" +static const char *wifi_pm_to_string(uint32_t pm) { + switch (pm) { + case CYW43_PERFORMANCE_PM: + return "NONE"; + case CYW43_DEFAULT_PM: + return "LIGHT"; + case CYW43_AGGRESSIVE_PM: + return "HIGH"; + default: + return "UNKNOWN"; + } +} +#endif // USE_TEXT_SENSOR && USE_WIFI + std::string DebugComponent::get_reset_reason_() { return ""; } uint32_t DebugComponent::get_free_heap_() { return rp2040.getFreeHeap(); } @@ -16,7 +44,18 @@ void DebugComponent::get_device_info_(std::string &device_info) { device_info += "CPU Frequency: " + to_string(rp2040.f_cpu()); } -void DebugComponent::update_platform_() {} +void DebugComponent::update_platform_() { +#if defined(USE_TEXT_SENSOR) && defined(USE_WIFI) + if (this->wifi_power_save_ != nullptr) { + uint32_t pm = cyw43_state.pm; + // Publish if the state has changed or if this is the first read + if (this->last_wifi_pm_ != pm || !this->wifi_power_save_->has_state()) { + this->wifi_power_save_->publish_state(wifi_pm_to_string(pm)); + this->last_wifi_pm_ = pm; + } + } +#endif +} } // namespace debug } // namespace esphome diff --git a/esphome/components/debug/text_sensor.py b/esphome/components/debug/text_sensor.py index 96ef2318501..11b0b4b7578 100644 --- a/esphome/components/debug/text_sensor.py +++ b/esphome/components/debug/text_sensor.py @@ -3,9 +3,11 @@ from esphome.components import text_sensor import esphome.config_validation as cv from esphome.const import ( CONF_DEVICE, + CONF_POWER_SAVE_MODE, ENTITY_CATEGORY_DIAGNOSTIC, ICON_CHIP, ICON_RESTART, + ICON_WIFI, ) from . import CONF_DEBUG_ID, DebugComponent @@ -25,6 +27,14 @@ CONFIG_SCHEMA = cv.Schema( icon=ICON_RESTART, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), + cv.Optional(CONF_POWER_SAVE_MODE): cv.All( + text_sensor.text_sensor_schema( + icon=ICON_WIFI, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.only_on(["esp32", "esp8266", "rp2040", "bk72xx", "rtl87xx"]), + cv.requires_component("wifi"), + ), } ) @@ -38,3 +48,6 @@ async def to_code(config): if CONF_RESET_REASON in config: sens = await text_sensor.new_text_sensor(config[CONF_RESET_REASON]) cg.add(debug_component.set_reset_reason_sensor(sens)) + if CONF_POWER_SAVE_MODE in config: + sens = await text_sensor.new_text_sensor(config[CONF_POWER_SAVE_MODE]) + cg.add(debug_component.set_wifi_power_save_sensor(sens)) diff --git a/tests/components/debug/test.bk72xx-ard.yaml b/tests/components/debug/test.bk72xx-ard.yaml index dade44d145b..b1b6cebd7c7 100644 --- a/tests/components/debug/test.bk72xx-ard.yaml +++ b/tests/components/debug/test.bk72xx-ard.yaml @@ -1 +1,10 @@ <<: !include common.yaml + +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" diff --git a/tests/components/debug/test.esp32-ard.yaml b/tests/components/debug/test.esp32-ard.yaml index 8e19a4d6277..ff6e34a8451 100644 --- a/tests/components/debug/test.esp32-ard.yaml +++ b/tests/components/debug/test.esp32-ard.yaml @@ -1,4 +1,13 @@ <<: !include common.yaml +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" + esp32: cpu_frequency: 240MHz diff --git a/tests/components/debug/test.esp32-idf.yaml b/tests/components/debug/test.esp32-idf.yaml index f7483a54b3b..96e16113922 100644 --- a/tests/components/debug/test.esp32-idf.yaml +++ b/tests/components/debug/test.esp32-idf.yaml @@ -3,6 +3,15 @@ esp32: cpu_frequency: 240MHz +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" + sensor: - platform: debug free: diff --git a/tests/components/debug/test.esp32-s2-idf.yaml b/tests/components/debug/test.esp32-s2-idf.yaml index dade44d145b..b1b6cebd7c7 100644 --- a/tests/components/debug/test.esp32-s2-idf.yaml +++ b/tests/components/debug/test.esp32-s2-idf.yaml @@ -1 +1,10 @@ <<: !include common.yaml + +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" diff --git a/tests/components/debug/test.esp8266-ard.yaml b/tests/components/debug/test.esp8266-ard.yaml index dade44d145b..b1b6cebd7c7 100644 --- a/tests/components/debug/test.esp8266-ard.yaml +++ b/tests/components/debug/test.esp8266-ard.yaml @@ -1 +1,10 @@ <<: !include common.yaml + +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" diff --git a/tests/components/debug/test.ln882x-ard.yaml b/tests/components/debug/test.ln882x-ard.yaml index dade44d145b..b1b6cebd7c7 100644 --- a/tests/components/debug/test.ln882x-ard.yaml +++ b/tests/components/debug/test.ln882x-ard.yaml @@ -1 +1,10 @@ <<: !include common.yaml + +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" diff --git a/tests/components/debug/test.nrf52-xiao-ble.yaml b/tests/components/debug/test.nrf52-xiao-ble.yaml index dade44d145b..b1b6cebd7c7 100644 --- a/tests/components/debug/test.nrf52-xiao-ble.yaml +++ b/tests/components/debug/test.nrf52-xiao-ble.yaml @@ -1 +1,10 @@ <<: !include common.yaml + +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" diff --git a/tests/components/debug/test.rp2040-ard.yaml b/tests/components/debug/test.rp2040-ard.yaml index dade44d145b..b1b6cebd7c7 100644 --- a/tests/components/debug/test.rp2040-ard.yaml +++ b/tests/components/debug/test.rp2040-ard.yaml @@ -1 +1,10 @@ <<: !include common.yaml + +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode" diff --git a/tests/components/fan/test.esp8266-ard.yaml b/tests/components/fan/test.esp8266-ard.yaml index dade44d145b..b1b6cebd7c7 100644 --- a/tests/components/fan/test.esp8266-ard.yaml +++ b/tests/components/fan/test.esp8266-ard.yaml @@ -1 +1,10 @@ <<: !include common.yaml + +wifi: + ssid: "WIFI SSID" + password: "WIFI PASSWORD" + +text_sensor: + - platform: debug + power_save_mode: + name: "WiFi Power Save Mode"