mirror of
https://github.com/esphome/esphome.git
synced 2026-05-25 02:16:13 +08:00
[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:
@@ -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)");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user