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"
|
KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results"
|
||||||
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
|
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
|
||||||
|
RUNTIME_ROAMING_SUPPRESSION_KEY = "wifi_runtime_roaming_suppression"
|
||||||
# Keys for listener counts
|
# Keys for listener counts
|
||||||
IP_STATE_LISTENERS_KEY = "wifi_ip_state_listeners"
|
IP_STATE_LISTENERS_KEY = "wifi_ip_state_listeners"
|
||||||
SCAN_RESULTS_LISTENERS_KEY = "wifi_scan_results_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
|
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:
|
def request_wifi_ip_state_listener() -> None:
|
||||||
"""Request an IP state listener slot."""
|
"""Request an IP state listener slot."""
|
||||||
CORE.data[IP_STATE_LISTENERS_KEY] = CORE.data.get(IP_STATE_LISTENERS_KEY, 0) + 1
|
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):
|
if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False):
|
||||||
cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE")
|
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
|
# Generate listener defines - each listener type has its own #ifdef
|
||||||
ip_state_count = CORE.data.get(IP_STATE_LISTENERS_KEY, 0)
|
ip_state_count = CORE.data.get(IP_STATE_LISTENERS_KEY, 0)
|
||||||
|
|||||||
@@ -822,7 +822,7 @@ void WiFiComponent::loop() {
|
|||||||
}
|
}
|
||||||
// else: scan in progress, wait
|
// else: scan in progress, wait
|
||||||
} else if (this->roaming_state_ == RoamingState::IDLE && this->roaming_attempts_ < ROAMING_MAX_ATTEMPTS &&
|
} 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);
|
this->check_roaming_(now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "esphome/core/string_ref.h"
|
#include "esphome/core/string_ref.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <limits>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -604,6 +606,49 @@ class WiFiComponent final : public Component {
|
|||||||
bool release_high_performance();
|
bool release_high_performance();
|
||||||
#endif // USE_WIFI_RUNTIME_POWER_SAVE
|
#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:
|
protected:
|
||||||
#ifdef USE_WIFI_AP
|
#ifdef USE_WIFI_AP
|
||||||
void setup_ap_config_();
|
void setup_ap_config_();
|
||||||
@@ -732,6 +777,15 @@ class WiFiComponent final : public Component {
|
|||||||
void process_roaming_scan_();
|
void process_roaming_scan_();
|
||||||
void clear_roaming_state_();
|
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
|
/// Free scan results memory unless a component needs them
|
||||||
void release_scan_results_();
|
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 limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS)
|
||||||
int8_t selected_sta_index_{-1};
|
int8_t selected_sta_index_{-1};
|
||||||
uint8_t roaming_attempts_{0};
|
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
|
#if USE_NETWORK_IPV6
|
||||||
uint8_t num_ipv6_addresses_{0};
|
uint8_t num_ipv6_addresses_{0};
|
||||||
#endif /* USE_NETWORK_IPV6 */
|
#endif /* USE_NETWORK_IPV6 */
|
||||||
|
|||||||
@@ -312,6 +312,7 @@
|
|||||||
#define ESPHOME_WIFI_CONNECT_STATE_LISTENERS 2
|
#define ESPHOME_WIFI_CONNECT_STATE_LISTENERS 2
|
||||||
#define ESPHOME_WIFI_POWER_SAVE_LISTENERS 2
|
#define ESPHOME_WIFI_POWER_SAVE_LISTENERS 2
|
||||||
#define USE_WIFI_RUNTIME_POWER_SAVE
|
#define USE_WIFI_RUNTIME_POWER_SAVE
|
||||||
|
#define USE_WIFI_RUNTIME_ROAMING_SUPPRESSION
|
||||||
#define USB_HOST_MAX_REQUESTS 16
|
#define USB_HOST_MAX_REQUESTS 16
|
||||||
#define USB_HOST_MAX_PACKET_SIZE 64
|
#define USB_HOST_MAX_PACKET_SIZE 64
|
||||||
#define USB_UART_OUTPUT_CHUNK_COUNT 5
|
#define USB_UART_OUTPUT_CHUNK_COUNT 5
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
psram:
|
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:
|
esphome:
|
||||||
platformio_options:
|
platformio_options:
|
||||||
build_flags:
|
build_flags:
|
||||||
- "-DUSE_WIFI_RUNTIME_POWER_SAVE"
|
- "-DUSE_WIFI_RUNTIME_POWER_SAVE"
|
||||||
|
- "-DUSE_WIFI_RUNTIME_ROAMING_SUPPRESSION"
|
||||||
on_boot:
|
on_boot:
|
||||||
- then:
|
- then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
esphome::wifi::global_wifi_component->request_high_performance();
|
esphome::wifi::global_wifi_component->request_high_performance();
|
||||||
esphome::wifi::global_wifi_component->release_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:
|
wifi:
|
||||||
use_psram: true
|
use_psram: true
|
||||||
|
|||||||
Reference in New Issue
Block a user