mirror of
https://github.com/esphome/esphome.git
synced 2026-02-28 18:04:19 -07:00
[core] Extend fast select optimization to LibreTiny platforms (#14254)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -149,9 +149,10 @@ def require_wake_loop_threadsafe() -> None:
|
||||
):
|
||||
CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True
|
||||
cg.add_define("USE_WAKE_LOOP_THREADSAFE")
|
||||
if not CORE.is_esp32:
|
||||
# Only non-ESP32 platforms need a UDP socket for wake notifications.
|
||||
# ESP32 uses FreeRTOS task notifications instead (no socket needed).
|
||||
if not CORE.is_esp32 and not CORE.is_libretiny:
|
||||
# Only platforms without fast select need a UDP socket for wake
|
||||
# notifications. ESP32 and LibreTiny use FreeRTOS task notifications
|
||||
# instead (no socket needed).
|
||||
consume_sockets(1, "socket.wake_loop_threadsafe", SocketType.UDP)({})
|
||||
|
||||
|
||||
@@ -187,6 +188,10 @@ async def to_code(config):
|
||||
elif impl == IMPLEMENTATION_BSD_SOCKETS:
|
||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
||||
cg.add_define("USE_SOCKET_SELECT_SUPPORT")
|
||||
# ESP32 and LibreTiny both have LwIP >= 2.1.3 with lwip_socket_dbg_get_socket()
|
||||
# and FreeRTOS task notifications — enable fast select to bypass lwip_select()
|
||||
if CORE.is_esp32 or CORE.is_libretiny:
|
||||
cg.add_define("USE_LWIP_FAST_SELECT")
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
|
||||
@@ -9,10 +9,17 @@
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_chip_info.h>
|
||||
#endif
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
#include "esphome/core/lwip_fast_select.h"
|
||||
#ifdef USE_ESP32
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#else
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
#endif
|
||||
#endif // USE_LWIP_FAST_SELECT
|
||||
#include "esphome/core/version.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <algorithm>
|
||||
@@ -147,14 +154,14 @@ void Application::setup() {
|
||||
clear_setup_priority_overrides();
|
||||
#endif
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_ESP32)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
|
||||
// Initialize fast select: saves main loop task handle for xTaskNotifyGive wake.
|
||||
// Always init on ESP32 — the fast path (rcvevent reads + ulTaskNotifyTake) is used
|
||||
// unconditionally when USE_SOCKET_SELECT_SUPPORT is enabled.
|
||||
// The fast path (rcvevent reads + ulTaskNotifyTake) is used unconditionally
|
||||
// when USE_LWIP_FAST_SELECT is enabled (ESP32 and LibreTiny).
|
||||
esphome_lwip_fast_select_init();
|
||||
#endif
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_ESP32)
|
||||
// Set up wake socket for waking main loop from tasks (non-ESP32 only)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
|
||||
// Set up wake socket for waking main loop from tasks (platforms without fast select only)
|
||||
this->setup_wake_loop_threadsafe_();
|
||||
#endif
|
||||
|
||||
@@ -532,7 +539,7 @@ void Application::enable_pending_loops_() {
|
||||
}
|
||||
|
||||
void Application::before_loop_tasks_(uint32_t loop_start_time) {
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_ESP32)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
|
||||
// Drain wake notifications first to clear socket for next wake
|
||||
this->drain_wake_notifications_();
|
||||
#endif
|
||||
@@ -585,7 +592,7 @@ bool Application::register_socket_fd(int fd) {
|
||||
#endif
|
||||
|
||||
this->socket_fds_.push_back(fd);
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
// Hook the socket's netconn callback for instant wake on receive events
|
||||
esphome_lwip_hook_socket(fd);
|
||||
#else
|
||||
@@ -609,12 +616,13 @@ void Application::unregister_socket_fd(int fd) {
|
||||
continue;
|
||||
|
||||
// Swap with last element and pop - O(1) removal since order doesn't matter.
|
||||
// No need to unhook the netconn callback on ESP32 — all LwIP sockets share
|
||||
// the same static event_callback, and the socket will be closed by the caller.
|
||||
// No need to unhook the netconn callback on fast select platforms — all LwIP
|
||||
// sockets share the same static event_callback, and the socket will be closed
|
||||
// by the caller.
|
||||
if (i < this->socket_fds_.size() - 1)
|
||||
this->socket_fds_[i] = this->socket_fds_.back();
|
||||
this->socket_fds_.pop_back();
|
||||
#ifndef USE_ESP32
|
||||
#ifndef USE_LWIP_FAST_SELECT
|
||||
this->socket_fds_changed_ = true;
|
||||
// Only recalculate max_fd if we removed the current max
|
||||
if (fd == this->max_fd_) {
|
||||
@@ -633,8 +641,8 @@ void Application::unregister_socket_fd(int fd) {
|
||||
|
||||
void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
// Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run.
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_ESP32)
|
||||
// ESP32 fast path: reads rcvevent directly via lwip_socket_dbg_get_socket() (~215 ns per socket).
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
|
||||
// Fast path (ESP32/LibreTiny): reads rcvevent directly via lwip_socket_dbg_get_socket().
|
||||
// Safe because this runs on the main loop which owns socket lifetime (create, read, close).
|
||||
if (delay_ms == 0) [[unlikely]] {
|
||||
yield();
|
||||
@@ -659,9 +667,8 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
|
||||
|
||||
#elif defined(USE_SOCKET_SELECT_SUPPORT)
|
||||
// Non-ESP32 select() path (LibreTiny bk72xx/rtl87xx, host platform).
|
||||
// ESP32 is excluded by the #if above — both BSD_SOCKETS and LWIP_SOCKETS on ESP32
|
||||
// use LwIP under the hood, so the fast path handles all ESP32 socket implementations.
|
||||
// Fallback select() path (host platform and any future platforms without fast select).
|
||||
// ESP32 and LibreTiny are excluded by the #if above — they use the fast path.
|
||||
if (!this->socket_fds_.empty()) [[likely]] {
|
||||
// Update fd_set if socket list has changed
|
||||
if (this->socket_fds_changed_) [[unlikely]] {
|
||||
@@ -725,12 +732,12 @@ alignas(Application) char app_storage[sizeof(Application)] asm("_ZN7esphome3AppE
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
void Application::wake_loop_threadsafe() {
|
||||
// Direct FreeRTOS task notification — <1 us, task context only (NOT ISR-safe)
|
||||
esphome_lwip_wake_main_loop();
|
||||
}
|
||||
#else // !USE_ESP32
|
||||
#else // !USE_LWIP_FAST_SELECT
|
||||
|
||||
void Application::setup_wake_loop_threadsafe_() {
|
||||
// Create UDP socket for wake notifications
|
||||
@@ -798,7 +805,7 @@ void Application::wake_loop_threadsafe() {
|
||||
lwip_send(this->wake_socket_fd_, &dummy, 1, 0);
|
||||
}
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
#endif // USE_LWIP_FAST_SELECT
|
||||
|
||||
#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
#include "esphome/core/lwip_fast_select.h"
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
@@ -511,10 +511,11 @@ class Application {
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
/// Fast path for Socket::ready() via friendship - skips negative fd check.
|
||||
/// Main loop only — on ESP32, reads rcvevent via lwip_socket_dbg_get_socket()
|
||||
/// which has no refcount; safe only because the main loop owns socket lifetime
|
||||
/// (creates, reads, and closes sockets on the same thread).
|
||||
#ifdef USE_ESP32
|
||||
/// Main loop only — with USE_LWIP_FAST_SELECT, reads rcvevent via
|
||||
/// lwip_socket_dbg_get_socket(), which has no refcount; safe only because
|
||||
/// the main loop owns socket lifetime (creates, reads, and closes sockets
|
||||
/// on the same thread).
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
bool is_socket_ready_(int fd) const { return esphome_lwip_socket_has_data(fd); }
|
||||
#else
|
||||
bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); }
|
||||
@@ -546,7 +547,7 @@ class Application {
|
||||
/// Perform a delay while also monitoring socket file descriptors for readiness
|
||||
void yield_with_select_(uint32_t delay_ms);
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_ESP32)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
|
||||
void setup_wake_loop_threadsafe_(); // Create wake notification socket
|
||||
inline void drain_wake_notifications_(); // Read pending wake notifications in main loop (hot path - inlined)
|
||||
#endif
|
||||
@@ -576,7 +577,7 @@ class Application {
|
||||
FixedVector<Component *> looping_components_{};
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
|
||||
#if defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_ESP32)
|
||||
#if defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
|
||||
int wake_socket_fd_{-1}; // Shared wake notification socket for waking main loop from tasks
|
||||
#endif
|
||||
#endif
|
||||
@@ -589,7 +590,7 @@ class Application {
|
||||
uint32_t last_loop_{0};
|
||||
uint32_t loop_component_start_time_{0};
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_ESP32)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
|
||||
int max_fd_{-1}; // Highest file descriptor number for select()
|
||||
#endif
|
||||
|
||||
@@ -605,12 +606,12 @@ class Application {
|
||||
bool in_loop_{false};
|
||||
volatile bool has_pending_enable_loop_requests_{false};
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_ESP32)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
|
||||
bool socket_fds_changed_{false}; // Flag to rebuild base_read_fds_ when socket_fds_ changes
|
||||
#endif
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_ESP32)
|
||||
// Variable-sized members (not needed on ESP32 — is_socket_ready_ reads rcvevent directly)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
|
||||
// Variable-sized members (not needed with fast select — is_socket_ready_ reads rcvevent directly)
|
||||
fd_set read_fds_{}; // Working fd_set: populated by select()
|
||||
fd_set base_read_fds_{}; // Cached fd_set rebuilt only when socket_fds_ changes
|
||||
#endif
|
||||
@@ -699,7 +700,7 @@ class Application {
|
||||
/// Global storage of Application pointer - only one Application can exist.
|
||||
extern Application App; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_ESP32)
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
|
||||
// Inline implementations for hot-path functions
|
||||
// drain_wake_notifications_() is called on every loop iteration
|
||||
|
||||
@@ -721,6 +722,6 @@ inline void Application::drain_wake_notifications_() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_ESP32)
|
||||
#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
#define USE_SENDSPIN_PORT 8928 // NOLINT
|
||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
#define USE_SOCKET_SELECT_SUPPORT
|
||||
#define USE_LWIP_FAST_SELECT
|
||||
#define USE_WAKE_LOOP_THREADSAFE
|
||||
#define USE_SPEAKER
|
||||
#define USE_SPI
|
||||
@@ -330,6 +331,7 @@
|
||||
#define USE_CAPTIVE_PORTAL
|
||||
#define USE_SOCKET_IMPL_LWIP_SOCKETS
|
||||
#define USE_SOCKET_SELECT_SUPPORT
|
||||
#define USE_LWIP_FAST_SELECT
|
||||
#define USE_WEBSERVER
|
||||
#define USE_WEBSERVER_AUTH
|
||||
#define USE_WEBSERVER_PORT 80 // NOLINT
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Fast socket monitoring for ESP32 (ESP-IDF LwIP)
|
||||
// Fast socket monitoring for ESP32 and LibreTiny (LwIP >= 2.1.3)
|
||||
// Replaces lwip_select() with direct rcvevent reads and FreeRTOS task notifications.
|
||||
//
|
||||
// This must be a .c file (not .cpp) because:
|
||||
// 1. lwip/priv/sockets_priv.h conflicts with C++ compilation units that include bootloader headers
|
||||
// 1. lwip/priv/sockets_priv.h conflicts with C++ compilation units
|
||||
// 2. The netconn callback is a C function pointer
|
||||
//
|
||||
// defines.h is force-included by the build system (-include flag), providing USE_ESP32 etc.
|
||||
// USE_ESP32 and USE_LIBRETINY platform flags (-D) control compilation of this file.
|
||||
// See the guard at the bottom of the header comment for details.
|
||||
//
|
||||
// Thread safety analysis
|
||||
// ======================
|
||||
@@ -81,20 +82,21 @@
|
||||
// Written by main loop in hook_socket(). Never restored — all LwIP sockets share
|
||||
// the same static event_callback (DEFAULT_SOCKET_EVENTCB), so the wrapper stays permanently.
|
||||
// Read by TCP/IP thread when invoking the callback.
|
||||
// Safe: 32-bit aligned pointer writes are atomic on Xtensa and RISC-V (ESP32).
|
||||
// The TCP/IP thread will see either the old or new pointer atomically — never a
|
||||
// torn value. Both the wrapper and original callbacks are valid at all times
|
||||
// (the wrapper itself calls the original), so either value is correct.
|
||||
// Safe: 32-bit aligned pointer writes are atomic on Xtensa, RISC-V (ESP32),
|
||||
// and ARM Cortex-M (LibreTiny). The TCP/IP thread will see either the old or
|
||||
// new pointer atomically — never a torn value. Both the wrapper and original
|
||||
// callbacks are valid at all times (the wrapper itself calls the original),
|
||||
// so either value is correct.
|
||||
//
|
||||
// sock->rcvevent (s16_t, 2 bytes):
|
||||
// Written by TCP/IP thread in event_callback under SYS_ARCH_PROTECT.
|
||||
// Read by main loop in has_data() via volatile cast.
|
||||
// Safe: SYS_ARCH_UNPROTECT releases a FreeRTOS mutex, which internally
|
||||
// uses a critical section with memory barrier (rsync on dual-core Xtensa; on
|
||||
// single-core builds the spinlock is compiled out, but cross-core visibility is
|
||||
// not an issue). The volatile cast prevents the compiler
|
||||
// from caching the read. Aligned 16-bit reads are single-instruction loads on
|
||||
// Xtensa (L16SI) and RISC-V (LH), which cannot produce torn values.
|
||||
// Safe: SYS_ARCH_UNPROTECT releases a FreeRTOS mutex (ESP32) or resumes the
|
||||
// scheduler (LibreTiny), both providing a memory barrier. The volatile cast
|
||||
// prevents the compiler from caching the read. Aligned 16-bit reads are
|
||||
// single-instruction loads on Xtensa (L16SI), RISC-V (LH), and ARM Cortex-M
|
||||
// (LDRH), which cannot produce torn values. On single-core chips (LibreTiny,
|
||||
// ESP32-C3/C6/H2) cross-core visibility is not an issue.
|
||||
//
|
||||
// FreeRTOS task notification value:
|
||||
// Written by TCP/IP thread (xTaskNotifyGive in callback) and background tasks
|
||||
@@ -103,20 +105,30 @@
|
||||
// critical sections). Multiple concurrent xTaskNotifyGive calls are safe —
|
||||
// the notification count simply increments.
|
||||
|
||||
#ifdef USE_ESP32
|
||||
// USE_ESP32 and USE_LIBRETINY are compiler -D flags, so they are always visible in this .c file.
|
||||
// Feature macros like USE_LWIP_FAST_SELECT may come from generated headers that are not included here,
|
||||
// so this implementation is enabled based on platform flags instead of USE_LWIP_FAST_SELECT.
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
|
||||
// LwIP headers must come first — they define netconn_callback, struct lwip_sock, etc.
|
||||
#include <lwip/api.h>
|
||||
#include <lwip/priv/sockets_priv.h>
|
||||
// FreeRTOS include paths differ: ESP-IDF uses freertos/ prefix, LibreTiny does not
|
||||
#ifdef USE_ESP32
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#else
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
#endif
|
||||
|
||||
#include "esphome/core/lwip_fast_select.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// Compile-time verification of thread safety assumptions.
|
||||
// On ESP32 (Xtensa/RISC-V), naturally-aligned reads/writes up to 32 bits are atomic.
|
||||
// On ESP32 (Xtensa/RISC-V) and LibreTiny (ARM Cortex-M), naturally-aligned
|
||||
// reads/writes up to 32 bits are atomic.
|
||||
// These asserts ensure our cross-thread shared state meets those requirements.
|
||||
|
||||
// Pointer types must fit in a single 32-bit store (atomic write)
|
||||
@@ -126,7 +138,7 @@ _Static_assert(sizeof(netconn_callback) <= 4, "netconn_callback must be <= 4 byt
|
||||
// rcvevent must fit in a single atomic read
|
||||
_Static_assert(sizeof(((struct lwip_sock *) 0)->rcvevent) <= 4, "rcvevent must be <= 4 bytes for atomic access");
|
||||
|
||||
// Struct member alignment — natural alignment guarantees atomicity on Xtensa/RISC-V.
|
||||
// Struct member alignment — natural alignment guarantees atomicity on Xtensa/RISC-V/ARM.
|
||||
// Misaligned access would not be atomic even if the size is <= 4 bytes.
|
||||
_Static_assert(offsetof(struct netconn, callback) % sizeof(netconn_callback) == 0,
|
||||
"netconn.callback must be naturally aligned for atomic access");
|
||||
@@ -183,9 +195,9 @@ bool esphome_lwip_socket_has_data(int fd) {
|
||||
return false;
|
||||
// volatile prevents the compiler from caching/reordering this cross-thread read.
|
||||
// The write side (TCP/IP thread) commits via SYS_ARCH_UNPROTECT which releases a
|
||||
// FreeRTOS mutex with a memory barrier (rsync on Xtensa), ensuring the value is
|
||||
// visible. Aligned 16-bit reads are single-instruction loads (L16SI/LH) on
|
||||
// Xtensa/RISC-V and cannot produce torn values.
|
||||
// FreeRTOS mutex (ESP32) or resumes the scheduler (LibreTiny), ensuring the value
|
||||
// is visible. Aligned 16-bit reads are single-instruction loads (L16SI/LH/LDRH) on
|
||||
// Xtensa/RISC-V/ARM and cannot produce torn values.
|
||||
return *(volatile s16_t *) &sock->rcvevent > 0;
|
||||
}
|
||||
|
||||
@@ -200,7 +212,7 @@ void esphome_lwip_hook_socket(int fd) {
|
||||
s_original_callback = sock->conn->callback;
|
||||
}
|
||||
|
||||
// Replace with our wrapper. Atomic on ESP32 (32-bit aligned pointer write).
|
||||
// Replace with our wrapper. Atomic on all supported platforms (32-bit aligned pointer write).
|
||||
// TCP/IP thread sees either old or new pointer — both are valid.
|
||||
sock->conn->callback = esphome_socket_event_callback;
|
||||
}
|
||||
@@ -213,4 +225,4 @@ void esphome_lwip_wake_main_loop(void) {
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_ESP32
|
||||
#endif // defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
// Fast socket monitoring for ESP32 (ESP-IDF LwIP)
|
||||
// Fast socket monitoring for ESP32 and LibreTiny (LwIP >= 2.1.3)
|
||||
// Replaces lwip_select() with direct rcvevent reads and FreeRTOS task notifications.
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import pytest
|
||||
|
||||
from esphome.components import socket
|
||||
from esphome.const import (
|
||||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RTL87XX,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
@@ -90,9 +95,15 @@ def test_require_wake_loop_threadsafe__no_networking_does_not_consume_socket() -
|
||||
assert udp_consumers == initial_udp
|
||||
|
||||
|
||||
def test_require_wake_loop_threadsafe__esp32_no_udp_socket() -> None:
|
||||
"""Test that ESP32 uses task notifications instead of UDP socket."""
|
||||
_setup_platform(PLATFORM_ESP32)
|
||||
@pytest.mark.parametrize(
|
||||
"platform",
|
||||
[PLATFORM_ESP32, PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_LN882X],
|
||||
)
|
||||
def test_require_wake_loop_threadsafe__fast_select_no_udp_socket(
|
||||
platform: str,
|
||||
) -> None:
|
||||
"""Test that fast select platforms use task notifications instead of UDP socket."""
|
||||
_setup_platform(platform)
|
||||
CORE.config = {"wifi": True}
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
@@ -100,13 +111,13 @@ def test_require_wake_loop_threadsafe__esp32_no_udp_socket() -> None:
|
||||
assert CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] is True
|
||||
assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines)
|
||||
|
||||
# Verify no UDP socket was consumed (ESP32 uses FreeRTOS task notifications)
|
||||
# Verify no UDP socket was consumed (fast select platforms use FreeRTOS task notifications)
|
||||
udp_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS_UDP, {})
|
||||
assert "socket.wake_loop_threadsafe" not in udp_consumers
|
||||
|
||||
|
||||
def test_require_wake_loop_threadsafe__non_esp32_consumes_udp_socket() -> None:
|
||||
"""Test that non-ESP32 platforms consume a UDP socket for wake notifications."""
|
||||
def test_require_wake_loop_threadsafe__non_fast_select_consumes_udp_socket() -> None:
|
||||
"""Test that platforms without fast select consume a UDP socket for wake notifications."""
|
||||
_setup_platform(PLATFORM_ESP8266)
|
||||
CORE.config = {"wifi": True}
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
Reference in New Issue
Block a user