mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 12:33:10 +00:00
[wifi] Add runtime suppression of post-connect roaming scans (#17012)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
This commit is contained in:
@@ -764,6 +764,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"
|
||||
RUNTIME_ROAMING_SUPPRESSION_KEY = "wifi_runtime_roaming_suppression"
|
||||
# Keys for listener counts
|
||||
IP_STATE_LISTENERS_KEY = "wifi_ip_state_listeners"
|
||||
SCAN_RESULTS_LISTENERS_KEY = "wifi_scan_results_listeners"
|
||||
@@ -794,6 +795,19 @@ def enable_runtime_power_save_control():
|
||||
CORE.data[RUNTIME_POWER_SAVE_KEY] = True
|
||||
|
||||
|
||||
def enable_runtime_roaming_suppression() -> None:
|
||||
"""Enable runtime suppression of post-connect roaming scans.
|
||||
|
||||
Components that are disrupted by the radio briefly going off-channel during a
|
||||
roaming scan (e.g., audio playback) should call this function during their code
|
||||
generation. This enables the request_roaming_suppression() and
|
||||
release_roaming_suppression() APIs, which pause periodic roaming scans while active.
|
||||
|
||||
Only supported on ESP32.
|
||||
"""
|
||||
CORE.data[RUNTIME_ROAMING_SUPPRESSION_KEY] = True
|
||||
|
||||
|
||||
def request_wifi_ip_state_listener() -> None:
|
||||
"""Request an IP state listener slot."""
|
||||
CORE.data[IP_STATE_LISTENERS_KEY] = CORE.data.get(IP_STATE_LISTENERS_KEY, 0) + 1
|
||||
@@ -827,6 +841,8 @@ async def final_step():
|
||||
)
|
||||
if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False):
|
||||
cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE")
|
||||
if CORE.data.get(RUNTIME_ROAMING_SUPPRESSION_KEY, False):
|
||||
cg.add_define("USE_WIFI_RUNTIME_ROAMING_SUPPRESSION")
|
||||
|
||||
# Generate listener defines - each listener type has its own #ifdef
|
||||
ip_state_count = CORE.data.get(IP_STATE_LISTENERS_KEY, 0)
|
||||
|
||||
@@ -822,7 +822,7 @@ void WiFiComponent::loop() {
|
||||
}
|
||||
// else: scan in progress, wait
|
||||
} else if (this->roaming_state_ == RoamingState::IDLE && this->roaming_attempts_ < ROAMING_MAX_ATTEMPTS &&
|
||||
now - this->roaming_last_check_ >= ROAMING_CHECK_INTERVAL) {
|
||||
now - this->roaming_last_check_ >= ROAMING_CHECK_INTERVAL && !this->roaming_suppressed_()) {
|
||||
this->check_roaming_(now);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#endif
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -604,6 +606,49 @@ class WiFiComponent final : public Component {
|
||||
bool release_high_performance();
|
||||
#endif // USE_WIFI_RUNTIME_POWER_SAVE
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION)
|
||||
/** Request that post-connect roaming scans be suppressed.
|
||||
*
|
||||
* Components that are disrupted by the radio briefly going off-channel during a
|
||||
* scan (e.g., audio playback) can call this to pause periodic roaming scans while
|
||||
* active. Multiple components can request suppression simultaneously; roaming
|
||||
* resumes once every requester has called release_roaming_suppression().
|
||||
*
|
||||
* A roaming scan already in progress is allowed to finish; this only prevents new
|
||||
* roaming scans from starting. The roaming interval timer is not reset, so roaming
|
||||
* resumes on the next loop once suppression is released (and the interval elapsed).
|
||||
*
|
||||
* Note: Only supported on ESP32.
|
||||
*
|
||||
* Thread-safe: may be called from any task.
|
||||
*/
|
||||
void request_roaming_suppression() {
|
||||
uint8_t current = this->roaming_suppression_count_.load(std::memory_order_relaxed);
|
||||
// CAS loop: saturate at max instead of wrapping, so an excess of requests can't roll the
|
||||
// counter back to zero and unintentionally re-enable roaming.
|
||||
while (current < std::numeric_limits<uint8_t>::max() &&
|
||||
!this->roaming_suppression_count_.compare_exchange_weak(current, current + 1, std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Release a roaming suppression request.
|
||||
*
|
||||
* Must be paired with a prior request_roaming_suppression() call. When all requests
|
||||
* are released (count reaches zero), post-connect roaming resumes. A release with no
|
||||
* outstanding request is ignored rather than underflowing the counter.
|
||||
*
|
||||
* Thread-safe: may be called from any task.
|
||||
*/
|
||||
void release_roaming_suppression() {
|
||||
uint8_t current = this->roaming_suppression_count_.load(std::memory_order_relaxed);
|
||||
// CAS loop: decrement only if non-zero, so an unmatched release can't wrap the counter
|
||||
// and permanently suppress roaming.
|
||||
while (current > 0 &&
|
||||
!this->roaming_suppression_count_.compare_exchange_weak(current, current - 1, std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
#endif // USE_ESP32 && USE_WIFI_RUNTIME_ROAMING_SUPPRESSION
|
||||
|
||||
protected:
|
||||
#ifdef USE_WIFI_AP
|
||||
void setup_ap_config_();
|
||||
@@ -732,6 +777,15 @@ class WiFiComponent final : public Component {
|
||||
void process_roaming_scan_();
|
||||
void clear_roaming_state_();
|
||||
|
||||
/// Returns true if a component has requested that roaming scans be suppressed (e.g. during audio playback).
|
||||
bool roaming_suppressed_() const {
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION)
|
||||
return this->roaming_suppression_count_.load(std::memory_order_relaxed) != 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Free scan results memory unless a component needs them
|
||||
void release_scan_results_();
|
||||
|
||||
@@ -845,6 +899,13 @@ class WiFiComponent final : public Component {
|
||||
// int8_t limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS)
|
||||
int8_t selected_sta_index_{-1};
|
||||
uint8_t roaming_attempts_{0};
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION)
|
||||
// Count of active roaming-suppression requests. Incremented/decremented from any task
|
||||
// (e.g. audio playback), read in loop(). Roaming scans are paused while non-zero.
|
||||
// Relaxed ordering is sufficient: the count value is the only data shared across threads,
|
||||
// so no happens-before relationship with other memory needs to be established.
|
||||
std::atomic<uint8_t> roaming_suppression_count_{0};
|
||||
#endif
|
||||
#if USE_NETWORK_IPV6
|
||||
uint8_t num_ipv6_addresses_{0};
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
@@ -312,6 +312,7 @@
|
||||
#define ESPHOME_WIFI_CONNECT_STATE_LISTENERS 2
|
||||
#define ESPHOME_WIFI_POWER_SAVE_LISTENERS 2
|
||||
#define USE_WIFI_RUNTIME_POWER_SAVE
|
||||
#define USE_WIFI_RUNTIME_ROAMING_SUPPRESSION
|
||||
#define USB_HOST_MAX_REQUESTS 16
|
||||
#define USB_HOST_MAX_PACKET_SIZE 64
|
||||
#define USB_UART_OUTPUT_CHUNK_COUNT 5
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
psram:
|
||||
|
||||
# Tests the high performance request and release; requires the USE_WIFI_RUNTIME_POWER_SAVE define
|
||||
# Tests the high performance and roaming suppression request/release APIs;
|
||||
# requires the USE_WIFI_RUNTIME_POWER_SAVE and USE_WIFI_RUNTIME_ROAMING_SUPPRESSION defines
|
||||
esphome:
|
||||
platformio_options:
|
||||
build_flags:
|
||||
- "-DUSE_WIFI_RUNTIME_POWER_SAVE"
|
||||
- "-DUSE_WIFI_RUNTIME_ROAMING_SUPPRESSION"
|
||||
on_boot:
|
||||
- then:
|
||||
- lambda: |-
|
||||
esphome::wifi::global_wifi_component->request_high_performance();
|
||||
esphome::wifi::global_wifi_component->release_high_performance();
|
||||
esphome::wifi::global_wifi_component->request_roaming_suppression();
|
||||
esphome::wifi::global_wifi_component->release_roaming_suppression();
|
||||
|
||||
wifi:
|
||||
use_psram: true
|
||||
|
||||
Reference in New Issue
Block a user