mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[deep_sleep] Add on_wake automation triggers (#17569)
This commit is contained in:
@@ -30,6 +30,7 @@ from esphome.const import (
|
||||
CONF_SECOND,
|
||||
CONF_SLEEP_DURATION,
|
||||
CONF_TIME_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WAKEUP_PIN,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
@@ -234,6 +235,15 @@ EXT1_WAKEUP_MODES = {
|
||||
}
|
||||
WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration")
|
||||
|
||||
WakeupCause = deep_sleep_ns.enum("WakeupCause")
|
||||
WakeTrigger = deep_sleep_ns.class_(
|
||||
"WakeTrigger", automation.Trigger.template(WakeupCause), cg.Component
|
||||
)
|
||||
Ext1WakeTrigger = deep_sleep_ns.class_(
|
||||
"Ext1WakeTrigger", automation.Trigger.template(), cg.Component
|
||||
)
|
||||
|
||||
CONF_ON_WAKE = "on_wake"
|
||||
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
|
||||
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
|
||||
CONF_TOUCH_WAKEUP = "touch_wakeup"
|
||||
@@ -256,6 +266,22 @@ WAKEUP_PIN_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
EXT1_WAKEUP_PIN_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema, validate_pin_number_esp32
|
||||
),
|
||||
cv.Optional(CONF_ON_WAKE): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Ext1WakeTrigger)}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
# Entries that are not in the {pin: ..., on_wake: ...} form are treated as a
|
||||
# bare pin config (the original syntax, e.g. a plain "GPIO5" or {number: 5}).
|
||||
validate_ext1_wakeup_pin = cv.maybe_simple_value(EXT1_WAKEUP_PIN_SCHEMA, key=CONF_PIN)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -282,8 +308,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PINS): cv.ensure_list(
|
||||
pins.internal_gpio_input_pin_schema,
|
||||
validate_pin_number_esp32,
|
||||
validate_ext1_wakeup_pin,
|
||||
),
|
||||
cv.Required(CONF_MODE): cv.All(
|
||||
cv.enum(EXT1_WAKEUP_MODES, upper=True),
|
||||
@@ -292,6 +317,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_ON_WAKE): cv.All(
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
|
||||
automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger)}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(
|
||||
cv.only_on_esp32,
|
||||
esp32.only_on_variant(
|
||||
@@ -362,16 +393,27 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.set_run_duration(wakeup_cause_to_run_duration))
|
||||
|
||||
if CONF_ESP32_EXT1_WAKEUP in config:
|
||||
conf = config[CONF_ESP32_EXT1_WAKEUP]
|
||||
if (ext1_conf := config.get(CONF_ESP32_EXT1_WAKEUP)) is not None:
|
||||
mask = 0
|
||||
for pin in conf[CONF_PINS]:
|
||||
mask |= 1 << pin[CONF_NUMBER]
|
||||
for pin_conf in ext1_conf[CONF_PINS]:
|
||||
number = pin_conf[CONF_PIN][CONF_NUMBER]
|
||||
mask |= 1 << number
|
||||
for wake_conf in pin_conf.get(CONF_ON_WAKE, []):
|
||||
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID], number)
|
||||
await cg.register_component(trigger, wake_conf)
|
||||
await automation.build_automation(trigger, [], wake_conf)
|
||||
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
|
||||
struct = cg.StructInitializer(
|
||||
Ext1Wakeup, ("mask", mask), ("wakeup_mode", conf[CONF_MODE])
|
||||
Ext1Wakeup, ("mask", mask), ("wakeup_mode", ext1_conf[CONF_MODE])
|
||||
)
|
||||
cg.add(var.set_ext1_wakeup(struct))
|
||||
|
||||
for wake_conf in config.get(CONF_ON_WAKE, []):
|
||||
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID])
|
||||
await cg.register_component(trigger, wake_conf)
|
||||
await automation.build_automation(trigger, [(WakeupCause, "cause")], wake_conf)
|
||||
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
|
||||
|
||||
if CONF_TOUCH_WAKEUP in config:
|
||||
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
|
||||
if CORE.using_zephyr and "zigbee" not in CORE.loaded_integrations:
|
||||
|
||||
@@ -7,6 +7,21 @@ namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep.bk72xx";
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
WakeupCause get_wakeup_cause() {
|
||||
switch (lt_get_reboot_reason()) {
|
||||
case REBOOT_REASON_SLEEP_GPIO:
|
||||
return WAKEUP_CAUSE_GPIO;
|
||||
case REBOOT_REASON_SLEEP_RTC:
|
||||
return WAKEUP_CAUSE_TIMER;
|
||||
case REBOOT_REASON_SLEEP_USB:
|
||||
return WAKEUP_CAUSE_UNKNOWN;
|
||||
default:
|
||||
return WAKEUP_CAUSE_NONE;
|
||||
}
|
||||
}
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||
|
||||
void DeepSleepComponent::dump_config_platform_() {
|
||||
|
||||
@@ -60,6 +60,65 @@ struct WakeupCauseToRunDuration {
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
/// Why the device woke from deep sleep. Passed to on_wake automations.
|
||||
enum WakeupCause : uint8_t {
|
||||
/// The device did not wake from deep sleep (for example a cold boot, reset or OTA restart).
|
||||
WAKEUP_CAUSE_NONE = 0,
|
||||
/// The device woke from deep sleep, but the source could not be identified.
|
||||
WAKEUP_CAUSE_UNKNOWN,
|
||||
/// The device was woken by the sleep timer.
|
||||
WAKEUP_CAUSE_TIMER,
|
||||
/// The device was woken by a GPIO pin (wakeup_pin or esp32_ext1_wakeup).
|
||||
WAKEUP_CAUSE_GPIO,
|
||||
/// The device was woken by a touch pad.
|
||||
WAKEUP_CAUSE_TOUCH,
|
||||
};
|
||||
|
||||
/// Return why the device woke from deep sleep. Implemented per platform.
|
||||
WakeupCause get_wakeup_cause();
|
||||
|
||||
/** Setup priority of on_wake triggers.
|
||||
*
|
||||
* Between restoring global variables (setup_priority::HARDWARE, 800) and on_boot automations at
|
||||
* their default priority (600), so on_wake automations can update state (e.g. globals) that
|
||||
* on_boot automations then use.
|
||||
*/
|
||||
inline constexpr float ON_WAKE_TRIGGER_SETUP_PRIORITY = 700.0f;
|
||||
|
||||
/// Fires once on boot when the device woke from deep sleep, with the wakeup cause.
|
||||
class WakeTrigger : public Trigger<WakeupCause>, public Component {
|
||||
public:
|
||||
void setup() override {
|
||||
const WakeupCause cause = get_wakeup_cause();
|
||||
if (cause != WAKEUP_CAUSE_NONE) {
|
||||
this->trigger(cause);
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
/// Fires once on boot when the device was woken from deep sleep by the given ext1 pin.
|
||||
class Ext1WakeTrigger : public Trigger<>, public Component {
|
||||
public:
|
||||
explicit Ext1WakeTrigger(uint8_t pin) : pin_(pin) {}
|
||||
void setup() override {
|
||||
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1 &&
|
||||
(esp_sleep_get_ext1_wakeup_status() & (1ULL << this->pin_))) {
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
|
||||
|
||||
protected:
|
||||
uint8_t pin_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
template<typename... Ts> class EnterDeepSleepAction;
|
||||
|
||||
template<typename... Ts> class PreventDeepSleepAction;
|
||||
|
||||
@@ -30,6 +30,25 @@ namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
WakeupCause get_wakeup_cause() {
|
||||
switch (esp_sleep_get_wakeup_cause()) {
|
||||
case ESP_SLEEP_WAKEUP_EXT0:
|
||||
case ESP_SLEEP_WAKEUP_EXT1:
|
||||
case ESP_SLEEP_WAKEUP_GPIO:
|
||||
return WAKEUP_CAUSE_GPIO;
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
return WAKEUP_CAUSE_TIMER;
|
||||
case ESP_SLEEP_WAKEUP_TOUCHPAD:
|
||||
return WAKEUP_CAUSE_TOUCH;
|
||||
case ESP_SLEEP_WAKEUP_UNDEFINED:
|
||||
return WAKEUP_CAUSE_NONE;
|
||||
default:
|
||||
return WAKEUP_CAUSE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
|
||||
if (this->wakeup_cause_to_run_duration_.has_value()) {
|
||||
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
|
||||
|
||||
@@ -3,10 +3,27 @@
|
||||
|
||||
#include <Esp.h>
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
extern "C" {
|
||||
#include <user_interface.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
WakeupCause get_wakeup_cause() {
|
||||
// The ESP8266 can only wake from deep sleep through the RTC timer (via GPIO16 -> RST).
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
if (ESP.getResetInfoPtr()->reason == REASON_DEEP_SLEEP_AWAKE) {
|
||||
return WAKEUP_CAUSE_TIMER;
|
||||
}
|
||||
return WAKEUP_CAUSE_NONE;
|
||||
}
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||
|
||||
void DeepSleepComponent::dump_config_platform_() {}
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#define USE_DATETIME_TIME
|
||||
#define USE_DEBUG
|
||||
#define USE_DEEP_SLEEP
|
||||
#define USE_DEEP_SLEEP_ON_WAKE
|
||||
#define USE_DEVICES
|
||||
#define USE_DISPLAY
|
||||
#define USE_ENTITY_DEVICE_CLASS
|
||||
|
||||
@@ -33,6 +33,43 @@ def test_deep_sleep_run_duration_simple(generate_main):
|
||||
assert "deepsleep->set_run_duration(10000);" in main_cpp
|
||||
|
||||
|
||||
def test_deep_sleep_on_wake_trigger(generate_main):
|
||||
"""
|
||||
When deep sleep is configured with a component-level on_wake automation,
|
||||
a WakeTrigger component should be registered with the wakeup cause as
|
||||
the automation argument.
|
||||
"""
|
||||
main_cpp = generate_main("tests/component_tests/deep_sleep/test_deep_sleep3.yaml")
|
||||
|
||||
assert "deep_sleep::WakeTrigger();" in main_cpp
|
||||
assert "Automation<deep_sleep::WakeupCause>" in main_cpp
|
||||
|
||||
|
||||
def test_deep_sleep_ext1_on_wake_triggers(generate_main):
|
||||
"""
|
||||
Each esp32_ext1_wakeup pin with an on_wake automation should get its own
|
||||
Ext1WakeTrigger with the pin number, and all pins (including the legacy
|
||||
bare-pin shorthand) should contribute to the ext1 wakeup mask.
|
||||
"""
|
||||
main_cpp = generate_main("tests/component_tests/deep_sleep/test_deep_sleep3.yaml")
|
||||
|
||||
assert "deep_sleep::Ext1WakeTrigger(2);" in main_cpp
|
||||
assert "deep_sleep::Ext1WakeTrigger(4);" in main_cpp
|
||||
# GPIO13 has no on_wake, so no trigger is created for it
|
||||
assert "deep_sleep::Ext1WakeTrigger(13)" not in main_cpp
|
||||
# mask covers GPIO2, GPIO4 and GPIO13
|
||||
assert ".mask = 8212," in main_cpp
|
||||
|
||||
|
||||
def test_deep_sleep_no_on_wake_no_triggers(generate_main):
|
||||
"""
|
||||
Without any on_wake automations, no wake trigger code should be generated.
|
||||
"""
|
||||
main_cpp = generate_main("tests/component_tests/deep_sleep/test_deep_sleep1.yaml")
|
||||
|
||||
assert "WakeTrigger" not in main_cpp
|
||||
|
||||
|
||||
def test_deep_sleep_run_duration_dictionary(generate_main):
|
||||
"""
|
||||
When deep sleep is configured with dictionary run duration, it should be set.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
|
||||
deep_sleep:
|
||||
id: deepsleep
|
||||
sleep_duration: 1min
|
||||
run_duration: 10s
|
||||
on_wake:
|
||||
- lambda: 'ESP_LOGD("test", "cause %d", static_cast<int>(cause));'
|
||||
esp32_ext1_wakeup:
|
||||
mode: ANY_HIGH
|
||||
pins:
|
||||
- pin: GPIO2
|
||||
on_wake:
|
||||
- lambda: 'ESP_LOGD("test", "left");'
|
||||
- pin:
|
||||
number: GPIO4
|
||||
on_wake:
|
||||
- lambda: 'ESP_LOGD("test", "right");'
|
||||
- number: GPIO13
|
||||
@@ -6,9 +6,15 @@ deep_sleep:
|
||||
sleep_duration: 50s
|
||||
wakeup_pin: ${wakeup_pin}
|
||||
wakeup_pin_mode: INVERT_WAKEUP
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
esp32_ext1_wakeup:
|
||||
pins:
|
||||
- number: GPIO2
|
||||
- pin: GPIO2
|
||||
on_wake:
|
||||
- logger.log: Woken by ext1 pin GPIO2
|
||||
- number: GPIO13
|
||||
mode: ANY_HIGH
|
||||
touch_wakeup: true
|
||||
|
||||
@@ -5,8 +5,15 @@ deep_sleep:
|
||||
sleep_duration: 50s
|
||||
wakeup_pin: ${wakeup_pin}
|
||||
wakeup_pin_mode: INVERT_WAKEUP
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
esp32_ext1_wakeup:
|
||||
pins:
|
||||
- number: GPIO2
|
||||
- pin:
|
||||
number: GPIO2
|
||||
on_wake:
|
||||
- logger.log: Woken by ext1 pin GPIO2
|
||||
- number: GPIO5
|
||||
mode: ANY_HIGH
|
||||
|
||||
@@ -5,3 +5,7 @@ deep_sleep:
|
||||
sleep_duration: 50s
|
||||
wakeup_pin: ${wakeup_pin}
|
||||
wakeup_pin_mode: INVERT_WAKEUP
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
deep_sleep:
|
||||
run_duration: 30s
|
||||
sleep_duration: 12h
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
wakeup_pin:
|
||||
- pin:
|
||||
number: P6
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
deep_sleep:
|
||||
run_duration: 10s
|
||||
sleep_duration: 50s
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
Reference in New Issue
Block a user