[core] Split wake.{h,cpp} into per-platform files under esphome/core/wake/

Move each platform branch out of wake.h/wake.cpp into its own
esphome/core/wake/wake_<platform>.{h,cpp} pair (wake_freertos for
ESP32/LibreTiny, plus wake_esp8266, wake_rp2040, wake_host, and
wake_generic for the Zephyr/NRF52 fallback). wake.h becomes a thin
dispatcher that defines only the cross-platform wake_request_set/
wake_request_take helpers and then #include's the right platform
header based on USE_*. wake.cpp is removed; each platform's
g_wake_requested (and g_main_loop_woke where applicable) now lives
in the translation unit that actually uses it, guarded by the same
USE_* check.

Because esphome/core/ source discovery was previously flat, add an
opt-in recursive_sources flag to ComponentManifest and enable it only
on the core ("esphome") manifest. Subdirectories that are themselves
Python subpackages are still skipped so components (which already
register platform subpackages independently) are unaffected.
This commit is contained in:
J. Nick Koston
2026-04-24 12:32:55 -05:00
parent 9caf9ee023
commit f446c0e699
12 changed files with 422 additions and 293 deletions
+19 -195
View File
@@ -3,6 +3,10 @@
/// @file wake.h
/// Platform-specific main loop wake primitives.
/// Always available on all platforms — no opt-in needed.
///
/// The public API for callers lives here; the per-platform implementations
/// live under esphome/core/wake/ and are included at the bottom of this file
/// based on the active USE_* platform define.
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
@@ -11,21 +15,6 @@
#include <atomic>
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#include "esphome/core/main_task.h"
#endif
#ifdef USE_ESP8266
#include <coredecls.h>
#elif defined(USE_RP2040)
#include <hardware/sync.h>
#include <pico/time.h>
#endif
#ifdef USE_HOST
#include <sys/select.h>
#include <sys/socket.h>
#endif
namespace esphome {
// === Wake flag for ESP8266/RP2040 ===
@@ -67,184 +56,19 @@ __attribute__((always_inline)) inline bool wake_request_take() {
}
#endif
// === ESP32 / LibreTiny (FreeRTOS) ===
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
/// Wake the main loop from any context (ISR or task).
/// always_inline so callers placed in IRAM keep the whole wake path in IRAM.
__attribute__((always_inline)) inline void wake_main_task_any_context() {
// Set the wake-requested flag BEFORE the task notification so the consumer
// (Application::loop() gate) is guaranteed to see it on its next gate check.
wake_request_set();
if (in_isr_context()) {
BaseType_t px_higher_priority_task_woken = pdFALSE;
esphome_main_task_notify_from_isr(&px_higher_priority_task_woken);
#ifdef portYIELD_FROM_ISR
portYIELD_FROM_ISR(px_higher_priority_task_woken);
#else
// ARM9 FreeRTOS port (BK72xx) does not define portYIELD_FROM_ISR; the IRQ
// exit sequence performs the context switch if one was requested.
(void) px_higher_priority_task_woken;
#endif
} else {
esphome_main_task_notify();
}
}
/// IRAM_ATTR entry points — defined in wake.cpp.
void wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken);
void wake_loop_any_context();
inline void wake_loop_threadsafe() {
wake_request_set();
esphome_main_task_notify();
}
namespace internal {
inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
// Fast path (with USE_LWIP_FAST_SELECT): FreeRTOS task notifications posted by the lwip
// event_callback wrapper (see lwip_fast_select.c) are the single source of truth for
// socket wake-ups. Every NETCONN_EVT_RCVPLUS posts an xTaskNotifyGive, so any notification
// that lands between wakes keeps the counter non-zero (next ulTaskNotifyTake returns
// immediately) or wakes a blocked Take directly. Additional wake sources:
// wake_loop_threadsafe() from background tasks, and the ms timeout.
if (ms == 0) [[unlikely]] {
yield();
return;
}
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(ms));
}
} // namespace internal
// === ESP8266 ===
#elif defined(USE_ESP8266)
/// Inline implementation — IRAM callers inline this directly.
inline void ESPHOME_ALWAYS_INLINE wake_loop_impl() {
// Set the wake-requested flag BEFORE esp_schedule so the consumer is
// guaranteed to see it on its next gate check.
wake_request_set();
g_main_loop_woke = true;
esp_schedule();
}
/// IRAM_ATTR entry point for ISR callers — defined in wake.cpp.
void wake_loop_any_context();
/// Non-ISR: always inline.
inline void wake_loop_threadsafe() { wake_loop_impl(); }
/// ISR-safe: no task_woken arg because ESP8266 has no FreeRTOS. Caller must be IRAM_ATTR.
inline void ESPHOME_ALWAYS_INLINE wake_loop_isrsafe() { wake_loop_impl(); }
namespace internal {
inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
delay(0);
return;
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
return;
}
esp_delay(ms, []() { return !g_main_loop_woke; });
}
} // namespace internal
// === RP2040 ===
#elif defined(USE_RP2040)
inline void wake_loop_any_context() {
// Set the wake-requested flag BEFORE the SEV so the consumer is guaranteed
// to see it on its next gate check.
wake_request_set();
g_main_loop_woke = true;
__sev();
}
inline void wake_loop_threadsafe() { wake_loop_any_context(); }
/// RP2040 wakeable delay uses file-scope state (alarm callback + flag) — defined in wake.cpp.
namespace internal {
void wakeable_delay(uint32_t ms);
} // namespace internal
// === Host / Zephyr / other ===
#else
#ifdef USE_HOST
/// Host: wakes select() via UDP loopback socket. Defined in wake.cpp.
void wake_loop_threadsafe();
/// Register a socket file descriptor with the host select() loop. Not
/// thread-safe — main loop only. Returns false if fd is invalid or
/// >= FD_SETSIZE.
bool wake_register_fd(int fd);
/// Unregister a socket file descriptor. Not thread-safe — main loop only.
void wake_unregister_fd(int fd);
/// One-time setup of the loopback wake socket. Called from Application::setup().
void wake_setup();
// wake_fd_ready() and wake_drain_notifications() are defined inline at the
// bottom of this file — they need internal::g_read_fds / g_wake_socket_fd in
// scope, which depend on USE_HOST-only includes pulled in above.
#else
/// Zephyr is currently the only platform without a wake mechanism.
/// wake_loop_threadsafe() is a no-op and wakeable_delay() falls back to delay().
/// TODO: implement proper Zephyr wake using k_poll / k_sem or similar.
inline void wake_loop_threadsafe() {}
#endif
inline void wake_loop_any_context() { wake_loop_threadsafe(); }
namespace internal {
#ifdef USE_HOST
/// Host wakeable_delay uses select() over the registered fds — defined in wake.cpp.
void wakeable_delay(uint32_t ms);
#else
inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
yield();
return;
}
delay(ms);
}
#endif
} // namespace internal
#endif
#ifdef USE_HOST
namespace internal {
// File-scope state owned by wake.cpp. Accessed inline by wake_drain_notifications()
// and wake_fd_ready() so the hot path stays in the header.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern int g_wake_socket_fd;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern fd_set g_read_fds;
} // namespace internal
inline bool ESPHOME_ALWAYS_INLINE wake_fd_ready(int fd) { return FD_ISSET(fd, &internal::g_read_fds); }
// Small buffer for draining wake notification bytes (1 byte sent per wake).
// Sized to drain multiple notifications per recvfrom() without wasting stack.
inline constexpr size_t WAKE_NOTIFY_DRAIN_BUFFER_SIZE = 16;
inline void ESPHOME_ALWAYS_INLINE wake_drain_notifications() {
// Called from main loop to drain any pending wake notifications.
// Must check wake_fd_ready() to avoid blocking on empty socket.
if (internal::g_wake_socket_fd >= 0 && wake_fd_ready(internal::g_wake_socket_fd)) {
char buffer[WAKE_NOTIFY_DRAIN_BUFFER_SIZE];
// Drain all pending notifications with non-blocking reads. Multiple wake events
// may have triggered multiple writes, so drain until EWOULDBLOCK. We control
// both ends of this loopback socket (always 1 byte per wake), so no error
// checking — any error indicates catastrophic system failure.
while (::recvfrom(internal::g_wake_socket_fd, buffer, sizeof(buffer), 0, nullptr, nullptr) > 0) {
}
}
}
#endif // USE_HOST
} // namespace esphome
// Per-platform implementations. Each header re-enters namespace esphome {} and
// guards its body with the matching USE_* check, so only one contributes code
// for the active target.
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#include "esphome/core/wake/wake_freertos.h"
#elif defined(USE_ESP8266)
#include "esphome/core/wake/wake_esp8266.h"
#elif defined(USE_RP2040)
#include "esphome/core/wake/wake_rp2040.h"
#elif defined(USE_HOST)
#include "esphome/core/wake/wake_host.h"
#else
#include "esphome/core/wake/wake_generic.h"
#endif
+21
View File
@@ -0,0 +1,21 @@
#include "esphome/core/defines.h"
#ifdef USE_ESP8266
#include "esphome/core/hal.h"
#include "esphome/core/wake.h"
namespace esphome {
// === Wake-requested flag + main-loop woke flag storage ===
// ESP8266 is always ESPHOME_THREAD_SINGLE.
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
volatile bool g_main_loop_woke = false;
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
void IRAM_ATTR wake_loop_any_context() { wake_loop_impl(); }
} // namespace esphome
#endif // USE_ESP8266
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ESP8266
#include "esphome/core/hal.h"
#include <coredecls.h>
namespace esphome {
/// Inline implementation — IRAM callers inline this directly.
inline void ESPHOME_ALWAYS_INLINE wake_loop_impl() {
// Set the wake-requested flag BEFORE esp_schedule so the consumer is
// guaranteed to see it on its next gate check.
wake_request_set();
g_main_loop_woke = true;
esp_schedule();
}
/// IRAM_ATTR entry point for ISR callers — defined in wake_esp8266.cpp.
void wake_loop_any_context();
/// Non-ISR: always inline.
inline void wake_loop_threadsafe() { wake_loop_impl(); }
/// ISR-safe: no task_woken arg because ESP8266 has no FreeRTOS. Caller must be IRAM_ATTR.
inline void ESPHOME_ALWAYS_INLINE wake_loop_isrsafe() { wake_loop_impl(); }
namespace internal {
inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
delay(0);
return;
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
return;
}
esp_delay(ms, []() { return !g_main_loop_woke; });
}
} // namespace internal
} // namespace esphome
#endif // USE_ESP8266
+33
View File
@@ -0,0 +1,33 @@
#include "esphome/core/defines.h"
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#include "esphome/core/hal.h"
#include "esphome/core/wake.h"
namespace esphome {
// === Wake-requested flag storage ===
// ESP32 is always MULTI_ATOMICS; LibreTiny is MULTI_ATOMICS on chips with
// proper atomics (e.g. RTL8720) and MULTI_NO_ATOMICS on others (e.g. BK72XX).
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::atomic<uint8_t> g_wake_requested{0};
#else
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
#endif
void IRAM_ATTR wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken) {
// ISR-safe: set flag before notify so the wake is visible on the next gate
// check. wake_request_set() is just an aligned 8-bit store / atomic store
// and is safe from IRAM.
wake_request_set();
esphome_main_task_notify_from_isr(px_higher_priority_task_woken);
}
void IRAM_ATTR wake_loop_any_context() { wake_main_task_any_context(); }
} // namespace esphome
#endif // USE_ESP32 || USE_LIBRETINY
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#include "esphome/core/hal.h"
#include "esphome/core/main_task.h"
namespace esphome {
/// Wake the main loop from any context (ISR or task).
/// always_inline so callers placed in IRAM keep the whole wake path in IRAM.
__attribute__((always_inline)) inline void wake_main_task_any_context() {
// Set the wake-requested flag BEFORE the task notification so the consumer
// (Application::loop() gate) is guaranteed to see it on its next gate check.
wake_request_set();
if (in_isr_context()) {
BaseType_t px_higher_priority_task_woken = pdFALSE;
esphome_main_task_notify_from_isr(&px_higher_priority_task_woken);
#ifdef portYIELD_FROM_ISR
portYIELD_FROM_ISR(px_higher_priority_task_woken);
#else
// ARM9 FreeRTOS port (BK72xx) does not define portYIELD_FROM_ISR; the IRQ
// exit sequence performs the context switch if one was requested.
(void) px_higher_priority_task_woken;
#endif
} else {
esphome_main_task_notify();
}
}
/// IRAM_ATTR entry points — defined in wake_freertos.cpp.
void wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken);
void wake_loop_any_context();
inline void wake_loop_threadsafe() {
wake_request_set();
esphome_main_task_notify();
}
namespace internal {
inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
// Fast path (with USE_LWIP_FAST_SELECT): FreeRTOS task notifications posted by the lwip
// event_callback wrapper (see lwip_fast_select.c) are the single source of truth for
// socket wake-ups. Every NETCONN_EVT_RCVPLUS posts an xTaskNotifyGive, so any notification
// that lands between wakes keeps the counter non-zero (next ulTaskNotifyTake returns
// immediately) or wakes a blocked Take directly. Additional wake sources:
// wake_loop_threadsafe() from background tasks, and the ms timeout.
if (ms == 0) [[unlikely]] {
yield();
return;
}
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(ms));
}
} // namespace internal
} // namespace esphome
#endif // USE_ESP32 || USE_LIBRETINY
+17
View File
@@ -0,0 +1,17 @@
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_LIBRETINY) && !defined(USE_ESP8266) && !defined(USE_RP2040) && \
!defined(USE_HOST)
#include "esphome/core/wake.h"
namespace esphome {
// === Wake-requested flag storage ===
// Fallback platforms (currently only Zephyr/NRF52) are ESPHOME_THREAD_SINGLE.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
} // namespace esphome
#endif // fallback guard
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_LIBRETINY) && !defined(USE_ESP8266) && !defined(USE_RP2040) && \
!defined(USE_HOST)
#include "esphome/core/hal.h"
namespace esphome {
/// Zephyr is currently the only platform without a wake mechanism.
/// wake_loop_threadsafe() is a no-op and wakeable_delay() falls back to delay().
/// TODO: implement proper Zephyr wake using k_poll / k_sem or similar.
inline void wake_loop_threadsafe() {}
inline void wake_loop_any_context() { wake_loop_threadsafe(); }
namespace internal {
inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
yield();
return;
}
delay(ms);
}
} // namespace internal
} // namespace esphome
#endif // fallback guard
@@ -1,12 +1,11 @@
#include "esphome/core/wake.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <coredecls.h>
#endif
#include "esphome/core/defines.h"
#ifdef USE_HOST
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/wake.h"
#include <arpa/inet.h>
#include <cerrno>
#include <fcntl.h>
@@ -15,88 +14,19 @@
#include <sys/socket.h>
#include <unistd.h>
#include <vector>
#endif
namespace esphome {
// === Wake-requested flag storage ===
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
// Host is always ESPHOME_THREAD_MULTI_ATOMICS.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::atomic<uint8_t> g_wake_requested{0};
#else
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
#endif
// === ESP32 / LibreTiny — IRAM_ATTR entry points ===
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
void IRAM_ATTR wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken) {
// ISR-safe: set flag before notify so the wake is visible on the next gate
// check. wake_request_set() is just an aligned 8-bit store / atomic store
// and is safe from IRAM.
wake_request_set();
esphome_main_task_notify_from_isr(px_higher_priority_task_woken);
}
void IRAM_ATTR wake_loop_any_context() { wake_main_task_any_context(); }
#endif
// === ESP8266 / RP2040 ===
#if defined(USE_ESP8266) || defined(USE_RP2040)
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
volatile bool g_main_loop_woke = false;
#endif
#ifdef USE_ESP8266
void IRAM_ATTR wake_loop_any_context() { wake_loop_impl(); }
#endif
// === RP2040 — wakeable_delay (needs file-scope state for alarm callback) ===
#ifdef USE_RP2040
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static volatile bool s_delay_expired = false;
static int64_t alarm_callback_(alarm_id_t id, void *user_data) {
(void) id;
(void) user_data;
s_delay_expired = true;
__sev();
return 0;
}
namespace internal {
void wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
yield();
return;
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
return;
}
s_delay_expired = false;
alarm_id_t alarm = add_alarm_in_ms(ms, alarm_callback_, nullptr, true);
if (alarm <= 0) {
delay(ms);
return;
}
while (!g_main_loop_woke && !s_delay_expired) {
__wfe();
}
if (!s_delay_expired)
cancel_alarm(alarm);
g_main_loop_woke = false;
}
} // namespace internal
#endif // USE_RP2040
// === Host (UDP loopback socket + select() based fd watcher) ===
#ifdef USE_HOST
static const char *const TAG = "wake";
namespace internal {
// File-scope state — referenced inline by wake_drain_notifications() and
// wake_fd_ready() in wake.h, and by the bodies in this file.
// wake_fd_ready() in wake_host.h, and by the bodies in this file.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
int g_wake_socket_fd = -1;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
@@ -271,6 +201,7 @@ void wake_setup() {
return;
}
}
#endif // USE_HOST
} // namespace esphome
#endif // USE_HOST
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_HOST
#include "esphome/core/hal.h"
#include <sys/select.h>
#include <sys/socket.h>
namespace esphome {
/// Host: wakes select() via UDP loopback socket. Defined in wake_host.cpp.
void wake_loop_threadsafe();
/// Register a socket file descriptor with the host select() loop. Not
/// thread-safe — main loop only. Returns false if fd is invalid or
/// >= FD_SETSIZE.
bool wake_register_fd(int fd);
/// Unregister a socket file descriptor. Not thread-safe — main loop only.
void wake_unregister_fd(int fd);
/// One-time setup of the loopback wake socket. Called from Application::setup().
void wake_setup();
inline void wake_loop_any_context() { wake_loop_threadsafe(); }
namespace internal {
/// Host wakeable_delay uses select() over the registered fds — defined in wake_host.cpp.
void wakeable_delay(uint32_t ms);
// File-scope state owned by wake_host.cpp. Accessed inline by
// wake_drain_notifications() and wake_fd_ready() so the hot path stays in the header.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern int g_wake_socket_fd;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern fd_set g_read_fds;
} // namespace internal
inline bool ESPHOME_ALWAYS_INLINE wake_fd_ready(int fd) { return FD_ISSET(fd, &internal::g_read_fds); }
// Small buffer for draining wake notification bytes (1 byte sent per wake).
// Sized to drain multiple notifications per recvfrom() without wasting stack.
inline constexpr size_t WAKE_NOTIFY_DRAIN_BUFFER_SIZE = 16;
inline void ESPHOME_ALWAYS_INLINE wake_drain_notifications() {
// Called from main loop to drain any pending wake notifications.
// Must check wake_fd_ready() to avoid blocking on empty socket.
if (internal::g_wake_socket_fd >= 0 && wake_fd_ready(internal::g_wake_socket_fd)) {
char buffer[WAKE_NOTIFY_DRAIN_BUFFER_SIZE];
// Drain all pending notifications with non-blocking reads. Multiple wake events
// may have triggered multiple writes, so drain until EWOULDBLOCK. We control
// both ends of this loopback socket (always 1 byte per wake), so no error
// checking — any error indicates catastrophic system failure.
while (::recvfrom(internal::g_wake_socket_fd, buffer, sizeof(buffer), 0, nullptr, nullptr) > 0) {
}
}
}
} // namespace esphome
#endif // USE_HOST
+58
View File
@@ -0,0 +1,58 @@
#include "esphome/core/defines.h"
#ifdef USE_RP2040
#include "esphome/core/hal.h"
#include "esphome/core/wake.h"
#include <hardware/sync.h>
#include <pico/time.h>
namespace esphome {
// === Wake-requested flag + main-loop woke flag storage ===
// RP2040 is always ESPHOME_THREAD_SINGLE.
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
volatile bool g_main_loop_woke = false;
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static volatile bool s_delay_expired = false;
static int64_t alarm_callback_(alarm_id_t id, void *user_data) {
(void) id;
(void) user_data;
s_delay_expired = true;
__sev();
return 0;
}
namespace internal {
void wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
yield();
return;
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
return;
}
s_delay_expired = false;
alarm_id_t alarm = add_alarm_in_ms(ms, alarm_callback_, nullptr, true);
if (alarm <= 0) {
delay(ms);
return;
}
while (!g_main_loop_woke && !s_delay_expired) {
__wfe();
}
if (!s_delay_expired)
cancel_alarm(alarm);
g_main_loop_woke = false;
}
} // namespace internal
} // namespace esphome
#endif // USE_RP2040
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_RP2040
#include "esphome/core/hal.h"
#include <hardware/sync.h>
#include <pico/time.h>
namespace esphome {
inline void wake_loop_any_context() {
// Set the wake-requested flag BEFORE the SEV so the consumer is guaranteed
// to see it on its next gate check.
wake_request_set();
g_main_loop_woke = true;
__sev();
}
inline void wake_loop_threadsafe() { wake_loop_any_context(); }
/// RP2040 wakeable delay uses file-scope state (alarm callback + flag) — defined in wake_rp2040.cpp.
namespace internal {
void wakeable_delay(uint32_t ms);
} // namespace internal
} // namespace esphome
#endif // USE_RP2040
+31 -19
View File
@@ -31,8 +31,9 @@ class FileResource:
class ComponentManifest:
def __init__(self, module: ModuleType):
def __init__(self, module: ModuleType, recursive_sources: bool = False):
self.module = module
self.recursive_sources = recursive_sources
@property
def package(self) -> str:
@@ -108,8 +109,10 @@ class ComponentManifest:
def resources(self) -> list[FileResource]:
"""Return a list of all file resources defined in the package of this component.
This will return all cpp source files that are located in the same folder as the
loaded .py file (does not look through subdirectories)
By default only files directly in the package directory are returned. Manifests
constructed with ``recursive_sources=True`` also descend into non-subpackage
subdirectories (subdirectories without an ``__init__.py``), so core code can
live under ``esphome/core/<group>/`` without every component paying the cost.
"""
ret: list[FileResource] = []
@@ -121,23 +124,30 @@ class ComponentManifest:
set(filter_source_files_func()) if filter_source_files_func else set()
)
# Process all resources
for resource in (
r.name
for r in importlib.resources.files(self.package).iterdir()
if r.is_file()
):
if Path(resource).suffix not in SOURCE_FILE_EXTENSIONS:
continue
if not importlib.resources.files(self.package).joinpath(resource).is_file():
# Not a resource = this is a directory (yeah this is confusing)
continue
root = importlib.resources.files(self.package)
# Skip excluded files
if resource in excluded_files:
continue
for child in root.iterdir():
name = child.name
if child.is_file():
if Path(name).suffix not in SOURCE_FILE_EXTENSIONS:
continue
if name in excluded_files:
continue
ret.append(FileResource(self.package, name))
elif self.recursive_sources and child.is_dir() and name != "__pycache__":
# Skip Python subpackages — they load as their own components.
if child.joinpath("__init__.py").is_file():
continue
for sub in child.iterdir():
if not sub.is_file():
continue
if Path(sub.name).suffix not in SOURCE_FILE_EXTENSIONS:
continue
resource = f"{name}/{sub.name}"
if resource in excluded_files:
continue
ret.append(FileResource(self.package, resource))
ret.append(FileResource(self.package, resource))
return ret
@@ -237,7 +247,9 @@ def get_platform(domain: str, platform: str) -> ComponentManifest | None:
_COMPONENT_CACHE: dict[str, ComponentManifest] = {}
CORE_COMPONENTS_PATH = (Path(__file__).parent / "components").resolve()
_COMPONENT_CACHE["esphome"] = ComponentManifest(esphome.core.config)
_COMPONENT_CACHE["esphome"] = ComponentManifest(
esphome.core.config, recursive_sources=True
)
def _replace_component_manifest(domain: str, manifest: ComponentManifest) -> None: