mirror of
https://github.com/esphome/esphome.git
synced 2026-01-10 04:00:51 -07:00
[wifi] Add runtime power saving mode control (#11478)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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 ¶ms) {
|
||||
SavedWifiFastConnectSettings fast_connect_save{};
|
||||
|
||||
@@ -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<>()};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user