Compare commits

...
Author SHA1 Message Date
J. Nick Koston 41a1689cda Merge remote-tracking branch 'origin/dev' into HEAD 2026-11-14 08:37:54 -06:00
J. Nick Koston 58463a3959 Merge remote-tracking branch 'origin/dev' into deep-sleep-enter-inline
# Conflicts:
#	tests/component_tests/deep_sleep/test_deep_sleep.py
2026-11-04 09:02:33 -06:00
J. Nick Koston dd469cd927 Merge remote-tracking branch 'origin/dev' into deep-sleep-enter-inline
Dev now passes apply lambdas their arguments by value automatically,
so the by_value flag added here is dropped.
2026-11-03 18:50:55 -06:00
J. Nick Koston 1faed06c61 Merge branch 'dev' into deep-sleep-enter-inline 2026-11-01 13:30:24 -10:00
J. Nick Koston cf03fc25eb Merge dev 2026-11-01 11:59:07 -10:00
J. Nick Koston f1643effd4 Merge remote-tracking branch 'upstream/dev' into deep-sleep-enter-inline
# Conflicts:
#	tests/component_tests/deep_sleep/test_deep_sleep.py
#	tests/component_tests/deep_sleep/test_deep_sleep_enter.yaml
2026-10-31 07:09:46 -10:00
J. Nick Koston c743924982 Merge remote-tracking branch 'origin/deep-sleep-enter-tests' into deep-sleep-enter-inline
# Conflicts:
#	tests/component_tests/deep_sleep/test_deep_sleep.py
2026-10-30 23:50:18 -10:00
J. Nick Koston 94a68fc939 [deep_sleep] Anchor lambda duration asserts to their action 2026-10-30 23:47:03 -10:00
J. Nick Koston defe20e84e [deep_sleep] Remove EnterDeepSleepAction 2026-10-30 23:25:42 -10:00
J. Nick Koston 6b907952f5 [core] Honour by_value in trigger filters and share the lambda branch 2026-10-30 23:13:28 -10:00
J. Nick Koston b314955fde [deep_sleep] Build deep_sleep.enter with register_apply_action
Add a by_value option to ApplyField and ApplyCall so a lambda keeps the
trigger's by-value arguments and is always called.
2026-10-30 23:08:14 -10:00
J. Nick Koston 702ef4952f [deep_sleep] Generate deep_sleep.enter as an inline action 2026-10-30 23:01:44 -10:00
J. Nick Koston 500f62d8bd [deep_sleep] Add tests for deep_sleep.enter code generation 2026-10-30 22:58:10 -10:00
5 changed files with 121 additions and 122 deletions

No files matched your search

+13 -25
View File
@@ -41,8 +41,7 @@ from esphome.const import (
PLATFORM_NRF52,
PlatformFramework,
)
from esphome.core import CORE, ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.core import CORE
from esphome.types import ConfigType
WAKEUP_PINS = {
@@ -266,7 +265,6 @@ def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
WakeupPinMode = deep_sleep_ns.enum("WakeupPinMode")
WAKEUP_PIN_MODES = WakeupPinMode.options(
@@ -498,31 +496,21 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All(
)
@automation.register_action(
automation.register_apply_action(
"deep_sleep.enter",
EnterDeepSleepAction,
DEEP_SLEEP_ENTER_SCHEMA,
synchronous=True,
automation.ApplyField(CONF_SLEEP_DURATION, "set_sleep_duration", cg.uint32),
automation.ApplyCall(
"set_sleep_until({}, {}, {}, {})",
(
(CONF_TIME_ID, time.RealTimeClock),
((CONF_UNTIL, CONF_HOUR), cg.uint8),
((CONF_UNTIL, CONF_MINUTE), cg.uint8),
((CONF_UNTIL, CONF_SECOND), cg.uint8),
),
),
automation.ApplyCall("begin_sleep(true)"),
)
async def deep_sleep_enter_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_SLEEP_DURATION in config:
template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.uint32)
cg.add(var.set_sleep_duration(template_))
if CONF_UNTIL in config:
until = config[CONF_UNTIL]
cg.add(var.set_until(until[CONF_HOUR], until[CONF_MINUTE], until[CONF_SECOND]))
await cg.add_variable(var.set_time, config[CONF_TIME_ID])
return var
for _name, _call in (
@@ -27,6 +27,45 @@ void DeepSleepComponent::schedule_sleep_() {
}
}
#ifdef USE_TIME
void DeepSleepComponent::set_sleep_until(time::RealTimeClock *clock, uint8_t hour, uint8_t minute, uint8_t second) {
auto time = clock->now();
const uint32_t timestamp_now = time.timestamp;
bool after_time = false;
if (time.hour > hour) {
after_time = true;
} else {
if (time.hour == hour) {
if (time.minute > minute) {
after_time = true;
} else {
if (time.minute == minute) {
if (time.second > second) {
after_time = true;
}
}
}
}
}
time.hour = hour;
time.minute = minute;
time.second = second;
time.recalc_timestamp_utc();
time_t timestamp = time.timestamp; // timestamp in local time zone
if (after_time)
timestamp += 60 * 60 * 24;
int32_t offset = ESPTime::timezone_offset();
timestamp -= offset; // Change timestamp to utc
const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
this->set_sleep_duration(ms_left);
}
#endif
void DeepSleepComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Deep sleep:");
if (this->sleep_duration_.has_value()) {
@@ -123,8 +123,6 @@ class Ext1WakeTrigger : public Trigger<>, public Component {
#endif // USE_DEEP_SLEEP_ON_WAKE
template<typename... Ts> class EnterDeepSleepAction;
/** This component allows setting up the node to go into deep sleep mode to conserve battery.
*
* To set this component up, first set *when* the deep sleep should trigger using set_run_cycles
@@ -135,6 +133,10 @@ class DeepSleepComponent final : public Component {
public:
/// Set the duration in ms the component should sleep once it's in deep sleep mode.
void set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; }
#ifdef USE_TIME
/// Set the sleep duration to end at the next hour:minute:second local time on the clock.
void set_sleep_until(time::RealTimeClock *clock, uint8_t hour, uint8_t minute, uint8_t second);
#endif
#if defined(USE_ESP32)
/** Set the pin to wake up to on the ESP32 once it's in deep sleep mode.
* Use the inverted property to set the wakeup level.
@@ -228,76 +230,4 @@ class DeepSleepComponent final : public Component {
extern bool global_has_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
template<typename... Ts> class EnterDeepSleepAction final : public Action<Ts...> {
public:
EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {}
TEMPLATABLE_VALUE(uint32_t, sleep_duration);
#ifdef USE_TIME
void set_until(uint8_t hour, uint8_t minute, uint8_t second) {
this->hour_ = hour;
this->minute_ = minute;
this->second_ = second;
}
void set_time(time::RealTimeClock *time) { this->time_ = time; }
#endif
void play(const Ts &...x) override {
if (this->sleep_duration_.has_value()) {
this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...));
}
#ifdef USE_TIME
if (this->hour_.has_value()) {
auto time = this->time_->now();
const uint32_t timestamp_now = time.timestamp;
bool after_time = false;
if (time.hour > this->hour_) {
after_time = true;
} else {
if (time.hour == this->hour_) {
if (time.minute > this->minute_) {
after_time = true;
} else {
if (time.minute == this->minute_) {
if (time.second > this->second_) {
after_time = true;
}
}
}
}
}
time.hour = *this->hour_;
time.minute = *this->minute_;
time.second = *this->second_;
time.recalc_timestamp_utc();
time_t timestamp = time.timestamp; // timestamp in local time zone
if (after_time)
timestamp += 60 * 60 * 24;
int32_t offset = ESPTime::timezone_offset();
timestamp -= offset; // Change timestamp to utc
const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
this->deep_sleep_->set_sleep_duration(ms_left);
}
#endif
this->deep_sleep_->begin_sleep(true);
}
protected:
DeepSleepComponent *deep_sleep_;
#ifdef USE_TIME
optional<uint8_t> hour_;
optional<uint8_t> minute_;
optional<uint8_t> second_;
time::RealTimeClock *time_;
#endif
};
} // namespace esphome::deep_sleep
@@ -243,59 +243,73 @@ def test_deep_sleep_enter_without_duration(
main_cpp = generate_main(ENTER_YAML)
assert (
"new(deep_sleep_enterdeepsleepaction_id) "
"deep_sleep::EnterDeepSleepAction<>(deepsleep);\n"
"new(automation_id) Automation<>(sleep_now);"
"static void esphome__applyaction_id__fn() {\n"
" ::deepsleep->begin_sleep(true);\n"
"}\n"
) in main_cpp
assert "new(applyaction_id) ApplyAction<esphome__applyaction_id__fn>();" in main_cpp
assert "EnterDeepSleepAction" not in main_cpp
def test_deep_sleep_enter_constant_duration(
generate_main: Callable[[str | Path], str],
) -> None:
"""A constant sleep_duration is set in milliseconds."""
"""A constant sleep_duration is set in milliseconds before the sleep begins."""
main_cpp = generate_main(ENTER_YAML)
assert (
"deep_sleep_enterdeepsleepaction_id_2->set_sleep_duration([]() -> uint32_t {\n"
" return 3600000;\n"
"});"
"static void esphome__applyaction_id_2__fn() {\n"
" ::deepsleep->set_sleep_duration(3600000);\n"
" ::deepsleep->begin_sleep(true);\n"
"}\n"
) in main_cpp
def test_deep_sleep_enter_lambda_duration(
generate_main: Callable[[str | Path], str],
) -> None:
"""A lambda sleep_duration is computed from the script parameters."""
"""A lambda sleep_duration is computed from the script parameters before the sleep begins."""
main_cpp = generate_main(ENTER_YAML)
assert re.search(
r"new\(deep_sleep_enterdeepsleepaction_id_3\) "
r"deep_sleep::EnterDeepSleepAction<uint32_t>\(deepsleep\);\n"
r"deep_sleep_enterdeepsleepaction_id_3->set_sleep_duration\("
r"\[\]\(uint32_t seconds\) -> uint32_t \{\n(?: #line [^\n]*\n)?"
r" return seconds \* 1000;\n",
r"static void esphome__applyaction_id_3__fn\("
r"const std::remove_cvref_t<uint32_t> & seconds\) \{\n"
r" ::deepsleep->set_sleep_duration\(\[\]\(uint32_t seconds\) -> uint32_t \{\n"
r"(?: #line [^\n]*\n)?"
r" return seconds \* 1000;\n"
r"(?: // ESPHOME_LINE_DIRECTIVE_RESET\n)?"
r" \}\(seconds\)\);\n"
r" ::deepsleep->begin_sleep\(true\);\n"
r"\}\n",
main_cpp,
)
@pytest.mark.parametrize(
("action_id", "body"),
("fn_id", "body"),
[
("deep_sleep_enterdeepsleepaction_id_4", "seconds *= 1000; return seconds;"),
("deep_sleep_enterdeepsleepaction_id_5", "return seconds *= 1000;"),
("applyaction_id_4", "seconds *= 1000; return seconds;"),
("applyaction_id_5", "return seconds *= 1000;"),
],
)
def test_deep_sleep_enter_lambda_takes_args_by_value(
generate_main: Callable[[str | Path], str], action_id: str, body: str
generate_main: Callable[[str | Path], str], fn_id: str, body: str
) -> None:
"""The sleep_duration lambda gets the script parameters by value, so it may modify them."""
"""The sleep_duration lambda gets the script parameters by value, so it may modify them.
It is called rather than reduced, so even a single return modifies only its own copy.
"""
main_cpp = generate_main(ENTER_YAML)
assert re.search(
action_id + r"->set_sleep_duration\("
r"\[\]\(uint32_t seconds\) -> uint32_t \{\n(?: #line [^\n]*\n)? "
r"static void esphome__"
+ fn_id
+ r"__fn\(const std::remove_cvref_t<uint32_t> & seconds\) \{\n"
r" ::deepsleep->set_sleep_duration\(\[\]\(uint32_t seconds\) -> uint32_t \{\n"
r"(?: #line [^\n]*\n)? "
+ re.escape(body)
+ "\n",
+ r"\n(?: // ESPHOME_LINE_DIRECTIVE_RESET\n)? \}\(seconds\)\);\n"
r" ::deepsleep->begin_sleep\(true\);\n\}\n",
main_cpp,
)
@@ -307,11 +321,34 @@ def test_deep_sleep_enter_until(
main_cpp = generate_main(ENTER_YAML)
assert (
"deep_sleep_enterdeepsleepaction_id_6->set_until(6, 30, 0);\n"
"deep_sleep_enterdeepsleepaction_id_6->set_time(sntp_time);"
"static void esphome__applyaction_id_6__fn() {\n"
" ::deepsleep->set_sleep_until(::sntp_time, 6, 30, 0);\n"
" ::deepsleep->begin_sleep(true);\n"
"}\n"
) in main_cpp
def test_deep_sleep_enter_merges_with_adjacent_actions(
generate_main: Callable[[str | Path], str],
) -> None:
"""deep_sleep.enter shares one generated function with the actions around it."""
main_cpp = generate_main(ENTER_YAML)
start = main_cpp.index(
"static void esphome__applyaction_id_7__fn(const int & esphome__running) {"
)
body = main_cpp[start : main_cpp.index("\n}\n", start)]
assert (
body.index("::deepsleep->allow_deep_sleep();")
< body.index("::deepsleep->begin_sleep(true);")
< body.index("::deepsleep->prevent_deep_sleep();")
)
assert (
"new(applyaction_id_7) ApplyRunAction<esphome__applyaction_id_7__fn>();"
in main_cpp
)
@pytest.mark.parametrize(
"variant", [VARIANT_ESP32H2, VARIANT_ESP32H21, VARIANT_ESP32S31]
)
@@ -46,3 +46,8 @@ script:
- deep_sleep.enter:
until: "06:30:00"
time_id: sntp_time
- id: allow_and_sleep
then:
- deep_sleep.allow: deepsleep
- deep_sleep.enter: deepsleep
- deep_sleep.prevent: deepsleep