mirror of
https://github.com/esphome/esphome.git
synced 2026-05-29 23:07:16 +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:
|
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
|
||||||
if not CORE.is_bk72xx:
|
if CORE.is_bk72xx:
|
||||||
return value
|
max_duration = core.TimePeriod(hours=36)
|
||||||
max_duration = core.TimePeriod(hours=36)
|
if value > max_duration:
|
||||||
if value > max_duration:
|
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
|
||||||
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
|
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
|
// 5 seconds for deep sleep to ensure clean disconnect from Home Assistant
|
||||||
static const uint32_t TEARDOWN_TIMEOUT_DEEP_SLEEP_MS = 5000;
|
static const uint32_t TEARDOWN_TIMEOUT_DEEP_SLEEP_MS = 5000;
|
||||||
|
|
||||||
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
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)
|
|
||||||
|
|
||||||
void DeepSleepComponent::setup() {
|
void DeepSleepComponent::setup() {
|
||||||
#ifdef USE_ZEPHYR
|
|
||||||
k_sem_init(&this->wakeup_sem_, 0, 1);
|
|
||||||
#endif
|
|
||||||
global_has_deep_sleep = true;
|
global_has_deep_sleep = true;
|
||||||
this->schedule_sleep_();
|
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_() {
|
void DeepSleepComponent::schedule_sleep_() {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include <esp_sleep.h>
|
#include <esp_sleep.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -15,10 +13,6 @@
|
|||||||
#include "esphome/core/time.h"
|
#include "esphome/core/time.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ZEPHYR
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -125,9 +119,6 @@ class DeepSleepComponent : public Component {
|
|||||||
|
|
||||||
void prevent_deep_sleep();
|
void prevent_deep_sleep();
|
||||||
void allow_deep_sleep();
|
void allow_deep_sleep();
|
||||||
#ifdef USE_ZEPHYR
|
|
||||||
void wakeup();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Returns nullopt if no run duration is set. Otherwise, returns the run
|
// 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_;
|
optional<uint32_t> run_duration_;
|
||||||
bool next_enter_deep_sleep_{false};
|
bool next_enter_deep_sleep_{false};
|
||||||
bool prevent_{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)
|
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(); }
|
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 deep_sleep
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
#include "deep_sleep_component.h"
|
#include "deep_sleep_component.h"
|
||||||
#ifdef USE_ZEPHYR
|
#ifdef USE_ZEPHYR
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/wake.h"
|
||||||
#include <zephyr/sys/poweroff.h>
|
#include <zephyr/sys/poweroff.h>
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/stats/stats.h>
|
|
||||||
#include <zephyr/pm/pm.h>
|
|
||||||
|
|
||||||
namespace esphome::deep_sleep {
|
namespace esphome::deep_sleep {
|
||||||
|
|
||||||
static const char *const TAG = "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_; }
|
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||||
|
|
||||||
void DeepSleepComponent::dump_config_platform_() {}
|
void DeepSleepComponent::dump_config_platform_() {}
|
||||||
@@ -19,9 +15,8 @@ void DeepSleepComponent::dump_config_platform_() {}
|
|||||||
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
|
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
|
||||||
|
|
||||||
void DeepSleepComponent::deep_sleep_() {
|
void DeepSleepComponent::deep_sleep_() {
|
||||||
k_timeout_t sleep_duration = K_FOREVER;
|
|
||||||
if (this->sleep_duration_.has_value()) {
|
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 {
|
} else {
|
||||||
#ifndef USE_ZIGBEE
|
#ifndef USE_ZIGBEE
|
||||||
// the device can be woken up through one of the following signals:
|
// 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.
|
// The system is reset when it wakes up from System OFF mode.
|
||||||
sys_poweroff();
|
sys_poweroff();
|
||||||
|
#else
|
||||||
|
esphome::internal::wakeable_delay(UINT32_MAX);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// It might wake up immediately if k_sem_give was called again after wake up
|
const bool woke = esphome::wake_request_take();
|
||||||
int ret = k_sem_take(&this->wakeup_sem_, sleep_duration);
|
if (woke) {
|
||||||
if (ret == 0) {
|
|
||||||
ESP_LOGD(TAG, "Woken up by another thread");
|
ESP_LOGD(TAG, "Woken up by another thread");
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "Timeout expired (normal sleep)");
|
ESP_LOGD(TAG, "Timeout expired (normal sleep)");
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
#include <zephyr/settings/settings.h>
|
#include <zephyr/settings/settings.h>
|
||||||
#include <zephyr/storage/flash_map.h>
|
#include <zephyr/storage/flash_map.h>
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#ifdef USE_DEEP_SLEEP
|
#include "esphome/core/wake.h"
|
||||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <zboss_api.h>
|
#include <zboss_api.h>
|
||||||
@@ -119,11 +117,7 @@ void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) {
|
|||||||
/* Set default response value. */
|
/* Set default response value. */
|
||||||
p_device_cb_param->status = RET_OK;
|
p_device_cb_param->status = RET_OK;
|
||||||
|
|
||||||
#ifdef USE_DEEP_SLEEP
|
esphome::wake_loop_threadsafe();
|
||||||
if (auto *ds = deep_sleep::global_deep_sleep.load()) {
|
|
||||||
ds->wakeup();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// endpoints are enumerated from 1
|
// endpoints are enumerated from 1
|
||||||
if (global_zigbee->callbacks_.size() >= endpoint) {
|
if (global_zigbee->callbacks_.size() >= endpoint) {
|
||||||
|
|||||||
@@ -370,6 +370,9 @@ class Application {
|
|||||||
#elif defined(USE_ESP8266)
|
#elif defined(USE_ESP8266)
|
||||||
/// Wake from ISR (ESP8266). No task_woken arg — no FreeRTOS. Caller must be IRAM_ATTR.
|
/// 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(); }
|
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
|
#endif
|
||||||
|
|
||||||
/// Wake from any context (ISR, thread, callback).
|
/// Wake from any context (ISR, thread, callback).
|
||||||
|
|||||||
@@ -812,7 +812,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
|||||||
"wake/wake_host.cpp": {
|
"wake/wake_host.cpp": {
|
||||||
PlatformFramework.HOST_NATIVE,
|
PlatformFramework.HOST_NATIVE,
|
||||||
},
|
},
|
||||||
"wake/wake_generic.cpp": {
|
"wake/wake_zephyr.cpp": {
|
||||||
PlatformFramework.NRF52_ZEPHYR,
|
PlatformFramework.NRF52_ZEPHYR,
|
||||||
},
|
},
|
||||||
# Note: lock_free_queue.h and event_pool.h are header files and don't need to be filtered
|
# 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"
|
#include "esphome/core/wake/wake_rp2040.h"
|
||||||
#elif defined(USE_HOST)
|
#elif defined(USE_HOST)
|
||||||
#include "esphome/core/wake/wake_host.h"
|
#include "esphome/core/wake/wake_host.h"
|
||||||
|
#elif defined(USE_ZEPHYR)
|
||||||
|
#include "esphome/core/wake/wake_zephyr.h"
|
||||||
#else
|
#else
|
||||||
#include "esphome/core/wake/wake_generic.h"
|
#error "wake.h: wake_loop_threadsafe() is not implemented for this platform"
|
||||||
#endif
|
#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