[wifi] Add runtime power saving mode control (#11478)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Kevin Ahrendt
2025-11-24 12:55:04 -05:00
committed by GitHub
parent c146d92425
commit d1a1bb446b
5 changed files with 160 additions and 2 deletions

View File

@@ -607,6 +607,7 @@ async def wifi_disable_to_code(config, action_id, template_arg, args):
KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results"
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
def request_wifi_scan_results():
@@ -619,13 +620,28 @@ def request_wifi_scan_results():
CORE.data[KEEP_SCAN_RESULTS_KEY] = True
def enable_runtime_power_save_control():
"""Enable runtime WiFi power save control.
Components that need to dynamically switch WiFi power saving on/off for latency
performance (e.g., audio streaming, large data transfers) should call this
function during their code generation. This enables the request_high_performance()
and release_high_performance() APIs.
Only supported on ESP32.
"""
CORE.data[RUNTIME_POWER_SAVE_KEY] = True
@coroutine_with_priority(CoroPriority.FINAL)
async def final_step():
"""Final code generation step to configure scan result retention."""
"""Final code generation step to configure optional WiFi features."""
if CORE.data.get(KEEP_SCAN_RESULTS_KEY, False):
cg.add(
cg.RawExpression("wifi::global_wifi_component->set_keep_scan_results(true)")
)
if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False):
cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE")
@automation.register_action(

View File

@@ -330,6 +330,19 @@ float WiFiComponent::get_setup_priority() const { return setup_priority::WIFI; }
void WiFiComponent::setup() {
this->wifi_pre_setup_();
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
// Create semaphore for high-performance mode requests
// Start at 0, increment on request, decrement on release
this->high_performance_semaphore_ = xSemaphoreCreateCounting(UINT32_MAX, 0);
if (this->high_performance_semaphore_ == nullptr) {
ESP_LOGE(TAG, "Failed semaphore");
}
// Store the configured power save mode as baseline
this->configured_power_save_ = this->power_save_;
#endif
if (this->enable_on_boot_) {
this->start();
} else {
@@ -371,6 +384,19 @@ void WiFiComponent::start() {
ESP_LOGV(TAG, "Setting Output Power Option failed");
}
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
// Synchronize power_save_ with semaphore state before applying
if (this->high_performance_semaphore_ != nullptr) {
UBaseType_t semaphore_count = uxSemaphoreGetCount(this->high_performance_semaphore_);
if (semaphore_count > 0) {
this->power_save_ = WIFI_POWER_SAVE_NONE;
this->is_high_performance_mode_ = true;
} else {
this->power_save_ = this->configured_power_save_;
this->is_high_performance_mode_ = false;
}
}
#endif
if (!this->wifi_apply_power_save_()) {
ESP_LOGV(TAG, "Setting Power Save Option failed");
}
@@ -525,6 +551,31 @@ void WiFiComponent::loop() {
}
}
}
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
// Check if power save mode needs to be updated based on high-performance requests
if (this->high_performance_semaphore_ != nullptr) {
// Semaphore count directly represents active requests (starts at 0, increments on request)
UBaseType_t semaphore_count = uxSemaphoreGetCount(this->high_performance_semaphore_);
if (semaphore_count > 0 && !this->is_high_performance_mode_) {
// Transition to high-performance mode (no power save)
ESP_LOGV(TAG, "Switching to high-performance mode (%" PRIu32 " active %s)", (uint32_t) semaphore_count,
semaphore_count == 1 ? "request" : "requests");
this->power_save_ = WIFI_POWER_SAVE_NONE;
if (this->wifi_apply_power_save_()) {
this->is_high_performance_mode_ = true;
}
} else if (semaphore_count == 0 && this->is_high_performance_mode_) {
// Restore to configured power save mode
ESP_LOGV(TAG, "Restoring power save mode to configured setting");
this->power_save_ = this->configured_power_save_;
if (this->wifi_apply_power_save_()) {
this->is_high_performance_mode_ = false;
}
}
}
#endif
}
WiFiComponent::WiFiComponent() { global_wifi_component = this; }
@@ -1567,7 +1618,12 @@ bool WiFiComponent::is_connected() {
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_;
}
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; }
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) {
this->power_save_ = power_save;
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
this->configured_power_save_ = power_save;
#endif
}
void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; }
@@ -1586,6 +1642,38 @@ bool WiFiComponent::is_esp32_improv_active_() {
#endif
}
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
bool WiFiComponent::request_high_performance() {
// Already configured for high performance - request satisfied
if (this->configured_power_save_ == WIFI_POWER_SAVE_NONE) {
return true;
}
// Semaphore initialization failed
if (this->high_performance_semaphore_ == nullptr) {
return false;
}
// Give the semaphore (non-blocking). This increments the count.
return xSemaphoreGive(this->high_performance_semaphore_) == pdTRUE;
}
bool WiFiComponent::release_high_performance() {
// Already configured for high performance - nothing to release
if (this->configured_power_save_ == WIFI_POWER_SAVE_NONE) {
return true;
}
// Semaphore initialization failed
if (this->high_performance_semaphore_ == nullptr) {
return false;
}
// Take the semaphore (non-blocking). This decrements the count.
return xSemaphoreTake(this->high_performance_semaphore_, 0) == pdTRUE;
}
#endif // USE_ESP32 && USE_WIFI_RUNTIME_POWER_SAVE
#ifdef USE_WIFI_FAST_CONNECT
bool WiFiComponent::load_fast_connect_settings_(WiFiAP &params) {
SavedWifiFastConnectSettings fast_connect_save{};

View File

@@ -49,6 +49,11 @@ extern "C" {
#include <WiFi.h>
#endif
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#endif
namespace esphome {
namespace wifi {
@@ -365,6 +370,37 @@ class WiFiComponent : public Component {
int32_t get_wifi_channel();
#ifdef USE_WIFI_RUNTIME_POWER_SAVE
/** Request high-performance mode (no power saving) for improved WiFi latency.
*
* Components that need maximum WiFi performance (e.g., audio streaming, large data transfers)
* can call this method to temporarily disable WiFi power saving. Multiple components can
* request high performance simultaneously using a counting semaphore.
*
* Power saving will be restored to the YAML-configured mode when all components have
* called release_high_performance().
*
* Note: Only supported on ESP32.
*
* @return true if request was satisfied (high-performance mode active or already configured),
* false if operation failed (semaphore error)
*/
bool request_high_performance();
/** Release a high-performance mode request.
*
* Should be called when a component no longer needs maximum WiFi latency.
* When all requests are released (semaphore count reaches zero), WiFi power saving
* is restored to the YAML-configured mode.
*
* Note: Only supported on ESP32.
*
* @return true if release was successful (or already in high-performance config),
* false if operation failed (semaphore error)
*/
bool release_high_performance();
#endif // USE_WIFI_RUNTIME_POWER_SAVE
protected:
#ifdef USE_WIFI_AP
void setup_ap_config_();
@@ -535,6 +571,12 @@ class WiFiComponent : public Component {
bool keep_scan_results_{false};
bool did_scan_this_cycle_{false};
bool skip_cooldown_next_cycle_{false};
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
WiFiPowerSaveMode configured_power_save_{WIFI_POWER_SAVE_NONE};
bool is_high_performance_mode_{false};
SemaphoreHandle_t high_performance_semaphore_{nullptr};
#endif
// Pointers at the end (naturally aligned)
Trigger<> *connect_trigger_{new Trigger<>()};

View File

@@ -210,6 +210,7 @@
#define USE_WEBSERVER_SORTING
#define USE_WIFI_11KV_SUPPORT
#define USE_WIFI_FAST_CONNECT
#define USE_WIFI_RUNTIME_POWER_SAVE
#define USB_HOST_MAX_REQUESTS 16
#ifdef USE_ARDUINO

View File

@@ -1,5 +1,16 @@
psram:
# Tests the high performance request and release; requires the USE_WIFI_RUNTIME_POWER_SAVE define
esphome:
platformio_options:
build_flags:
- "-DUSE_WIFI_RUNTIME_POWER_SAVE"
on_boot:
- then:
- lambda: |-
esphome::wifi::global_wifi_component->request_high_performance();
esphome::wifi::global_wifi_component->release_high_performance();
wifi:
use_psram: true
min_auth_mode: WPA