[nrf52] implement wake_loop_threadsafe/wakeable_delay (#16032)

Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
tomaszduda23
2026-04-28 18:35:12 +02:00
committed by GitHub
parent daf3f4d2f1
commit 968878a62d
12 changed files with 93 additions and 96 deletions
+8 -5
View File
@@ -193,11 +193,14 @@ def _validate_ex1_wakeup_mode(value):
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
if not CORE.is_bk72xx:
return value
max_duration = core.TimePeriod(hours=36)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
if CORE.is_bk72xx:
max_duration = core.TimePeriod(hours=36)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
elif CORE.using_zephyr:
max_duration = core.TimePeriod(days=49)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 49 days on Zephyr")
return value
@@ -9,18 +9,11 @@ static const char *const TAG = "deep_sleep";
// 5 seconds for deep sleep to ensure clean disconnect from Home Assistant
static const uint32_t TEARDOWN_TIMEOUT_DEEP_SLEEP_MS = 5000;
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
std::atomic<DeepSleepComponent *> global_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void DeepSleepComponent::setup() {
#ifdef USE_ZEPHYR
k_sem_init(&this->wakeup_sem_, 0, 1);
#endif
global_has_deep_sleep = true;
this->schedule_sleep_();
// It can be used from another thread for waking up the device.
// It should be called as last item in setup.
global_deep_sleep.store(this);
}
void DeepSleepComponent::schedule_sleep_() {
@@ -4,8 +4,6 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include <atomic>
#ifdef USE_ESP32
#include <esp_sleep.h>
#endif
@@ -15,10 +13,6 @@
#include "esphome/core/time.h"
#endif
#ifdef USE_ZEPHYR
#include <zephyr/kernel.h>
#endif
#include <cinttypes>
namespace esphome {
@@ -125,9 +119,6 @@ class DeepSleepComponent : public Component {
void prevent_deep_sleep();
void allow_deep_sleep();
#ifdef USE_ZEPHYR
void wakeup();
#endif
protected:
// Returns nullopt if no run duration is set. Otherwise, returns the run
@@ -167,9 +158,6 @@ class DeepSleepComponent : public Component {
optional<uint32_t> run_duration_;
bool next_enter_deep_sleep_{false};
bool prevent_{false};
#ifdef USE_ZEPHYR
k_sem wakeup_sem_;
#endif
};
extern bool global_has_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -256,8 +244,5 @@ template<typename... Ts> class AllowDeepSleepAction : public Action<Ts...>, publ
void play(const Ts &...x) override { this->parent_->allow_deep_sleep(); }
};
extern std::atomic<DeepSleepComponent *>
global_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace deep_sleep
} // namespace esphome
@@ -1,17 +1,13 @@
#include "deep_sleep_component.h"
#ifdef USE_ZEPHYR
#include "esphome/core/log.h"
#include "esphome/core/wake.h"
#include <zephyr/sys/poweroff.h>
#include <zephyr/kernel.h>
#include <zephyr/stats/stats.h>
#include <zephyr/pm/pm.h>
namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep";
void DeepSleepComponent::wakeup() { k_sem_give(&this->wakeup_sem_); }
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {}
@@ -19,9 +15,8 @@ void DeepSleepComponent::dump_config_platform_() {}
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
void DeepSleepComponent::deep_sleep_() {
k_timeout_t sleep_duration = K_FOREVER;
if (this->sleep_duration_.has_value()) {
sleep_duration = K_USEC(*this->sleep_duration_);
esphome::internal::wakeable_delay(static_cast<uint32_t>(*this->sleep_duration_ / 1000));
} else {
#ifndef USE_ZIGBEE
// the device can be woken up through one of the following signals:
@@ -33,11 +28,12 @@ void DeepSleepComponent::deep_sleep_() {
//
// The system is reset when it wakes up from System OFF mode.
sys_poweroff();
#else
esphome::internal::wakeable_delay(UINT32_MAX);
#endif
}
// It might wake up immediately if k_sem_give was called again after wake up
int ret = k_sem_take(&this->wakeup_sem_, sleep_duration);
if (ret == 0) {
const bool woke = esphome::wake_request_take();
if (woke) {
ESP_LOGD(TAG, "Woken up by another thread");
} else {
ESP_LOGD(TAG, "Timeout expired (normal sleep)");
+2 -8
View File
@@ -4,9 +4,7 @@
#include <zephyr/settings/settings.h>
#include <zephyr/storage/flash_map.h>
#include "esphome/core/hal.h"
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
#endif
#include "esphome/core/wake.h"
extern "C" {
#include <zboss_api.h>
@@ -119,11 +117,7 @@ void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) {
/* Set default response value. */
p_device_cb_param->status = RET_OK;
#ifdef USE_DEEP_SLEEP
if (auto *ds = deep_sleep::global_deep_sleep.load()) {
ds->wakeup();
}
#endif
esphome::wake_loop_threadsafe();
// endpoints are enumerated from 1
if (global_zigbee->callbacks_.size() >= endpoint) {
+3
View File
@@ -370,6 +370,9 @@ class Application {
#elif defined(USE_ESP8266)
/// Wake from ISR (ESP8266). No task_woken arg — no FreeRTOS. Caller must be IRAM_ATTR.
static void IRAM_ATTR ESPHOME_ALWAYS_INLINE wake_loop_isrsafe() { esphome::wake_loop_isrsafe(); }
#elif defined(USE_ZEPHYR)
/// Wake from ISR (Zephyr). No task_woken arg — k_sem_give() handles ISR scheduling internally.
static void wake_loop_isrsafe() { esphome::wake_loop_isrsafe(); }
#endif
/// Wake from any context (ISR, thread, callback).
+1 -1
View File
@@ -812,7 +812,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
"wake/wake_host.cpp": {
PlatformFramework.HOST_NATIVE,
},
"wake/wake_generic.cpp": {
"wake/wake_zephyr.cpp": {
PlatformFramework.NRF52_ZEPHYR,
},
# Note: lock_free_queue.h and event_pool.h are header files and don't need to be filtered
+3 -1
View File
@@ -69,6 +69,8 @@ __attribute__((always_inline)) inline bool wake_request_take() {
#include "esphome/core/wake/wake_rp2040.h"
#elif defined(USE_HOST)
#include "esphome/core/wake/wake_host.h"
#elif defined(USE_ZEPHYR)
#include "esphome/core/wake/wake_zephyr.h"
#else
#include "esphome/core/wake/wake_generic.h"
#error "wake.h: wake_loop_threadsafe() is not implemented for this platform"
#endif
-17
View File
@@ -1,17 +0,0 @@
#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
@@ -1,31 +0,0 @@
#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
+41
View File
@@ -0,0 +1,41 @@
#include "esphome/core/defines.h"
#ifdef USE_ZEPHYR
#include "esphome/core/hal.h"
#include "esphome/core/wake.h"
#include <zephyr/kernel.h>
namespace esphome {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
K_SEM_DEFINE(esphome_wake_sem, 0, 1);
// === Wake-requested flag storage ===
// Zephyr has preemptive threads and ISRs, so wake_loop_threadsafe() is genuinely
// called cross-context. volatile uint8_t is sufficient because: (1) Cortex-M
// 8-bit aligned store/load is a single non-tearing instruction, and (2) every
// producer pairs the store with k_sem_give() (release barrier) and the consumer
// pairs the load with k_sem_take() (acquire barrier).
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
void wake_loop_threadsafe() {
wake_request_set();
k_sem_give(&esphome_wake_sem);
}
namespace internal {
void wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
yield();
return;
}
k_sem_take(&esphome_wake_sem, ms == UINT32_MAX ? K_FOREVER : K_MSEC(ms));
}
} // namespace internal
} // namespace esphome
#endif // USE_ZEPHYR
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ZEPHYR
#include "esphome/core/hal.h"
namespace esphome {
/// Zephyr: wakes the main loop via k_sem_give(). Thread- and ISR-safe.
/// Defined in wake_zephyr.cpp.
void wake_loop_threadsafe();
inline void wake_loop_any_context() { wake_loop_threadsafe(); }
/// ISR-safe: no task_woken arg because Zephyr's k_sem_give() does its own ISR
/// scheduling. Forwards to wake_loop_threadsafe().
inline void wake_loop_isrsafe() { wake_loop_threadsafe(); }
namespace internal {
/// Zephyr wakeable_delay uses k_sem_take() with a timeout — defined in wake_zephyr.cpp.
void wakeable_delay(uint32_t ms);
} // namespace internal
} // namespace esphome
#endif // USE_ZEPHYR