[deep_sleep][gpio][core][power_supply] Prepare for gpio hold (2/3) (#18966)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
luar123
2026-10-09 11:32:24 -10:00
committed by GitHub
co-authored by J. Nick Koston J. Nick Koston
parent a613073ee3
commit a4a09043ec
10 changed files with 76 additions and 12 deletions
+1
View File
@@ -27,6 +27,7 @@ CONF_ENABLE_OTA_DOWNGRADE_PROTECTION = "enable_ota_downgrade_protection"
CONF_ENABLED = "enabled"
CONF_GYROSCOPE_ODR = "gyroscope_odr"
CONF_GYROSCOPE_RANGE = "gyroscope_range"
CONF_HOLD_STATE = "hold_state"
CONF_HOST = "host"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_IAQ = "iaq"
@@ -111,7 +111,17 @@ void DeepSleepComponent::deep_sleep_() {
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
// Single pin wakeup (ext0) - ESP32, S2, S3 only
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
#ifdef USE_GPIO_HOLD
// Variants without per-pin deep sleep hold need the global enable
gpio_deep_sleep_hold_en();
#else
if (this->wakeup_pin_ != nullptr)
gpio_deep_sleep_hold_en();
#endif
#endif
// Single pin wakeup (ext0) - ESP32, S2, S3 only
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
!defined(USE_ESP32_VARIANT_ESP32C5) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2)
@@ -124,11 +134,6 @@ void DeepSleepComponent::deep_sleep_() {
}
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
gpio_hold_en(gpio_pin);
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
gpio_deep_sleep_hold_en();
#endif
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
+8
View File
@@ -31,6 +31,14 @@ class ESP32InternalGPIOPin final : public InternalGPIOPin {
bool is_inverted() const override { return this->pin_flags_.inverted; }
gpio_num_t get_pin_num() const { return static_cast<gpio_num_t>(this->pin_); }
gpio_drive_cap_t get_drive_strength() const { return static_cast<gpio_drive_cap_t>(this->pin_flags_.drive_strength); }
#ifdef USE_GPIO_HOLD
bool is_held() const override {
if (!this->get_hold())
return false;
esp_reset_reason_t reason = esp_reset_reason();
return reason == ESP_RST_DEEPSLEEP || reason == ESP_RST_SW;
}
#endif
protected:
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
+24 -1
View File
@@ -1,8 +1,10 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import output
from esphome.components.const import CONF_HOLD_STATE
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PIN
from esphome.const import CONF_ID, CONF_PIN, CONF_POWER_SUPPLY
import esphome.final_validate as fv
from esphome.types import ConfigType
from .. import gpio_ns
@@ -17,6 +19,27 @@ CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend(
).extend(cv.COMPONENT_SCHEMA)
def _require_hold(ps_config: ConfigType) -> ConfigType:
if not ps_config[CONF_PIN].get(CONF_HOLD_STATE):
raise cv.Invalid(
f"The power supply pin must also set {CONF_HOLD_STATE} "
f"when an output it powers sets it",
[CONF_PIN],
)
return ps_config
def _final_validate(config: ConfigType) -> None:
if not config[CONF_PIN].get(CONF_HOLD_STATE):
return
if (ps_id := config.get(CONF_POWER_SUPPLY)) is None:
return
fv.id_declaration_match_schema(_require_hold)(ps_id)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await output.register_output(var, config)
@@ -11,9 +11,12 @@ class GPIOBinaryOutput final : public output::BinaryOutput, public Component {
void set_pin(GPIOPin *pin) { pin_ = pin; }
void setup() override {
this->turn_off();
const bool held = this->pin_->is_held();
if (!held)
this->turn_off();
this->pin_->setup();
this->turn_off();
if (!held)
this->turn_off();
}
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
@@ -12,8 +12,9 @@ float GPIOSwitch::get_setup_priority() const { return setup_priority::HARDWARE;
void GPIOSwitch::setup() {
bool initial_state = this->get_initial_state_with_restore_mode().value_or(false);
// write state before setup
this->control(initial_state);
// write state before setup unless the pin kept it across the reset
if (!this->pin_->is_held())
this->control(initial_state);
this->pin_->setup();
// write after setup again for other IOs
this->control(initial_state);
@@ -9,7 +9,12 @@ static constexpr uint32_t POWER_OFF_TIMEOUT_ID = 0;
void PowerSupply::setup() {
this->pin_->setup();
this->pin_->digital_write(false);
if (this->pin_->is_held()) {
// Rail stayed on across the reset; drop it unless something asks for it
this->schedule_off_if_idle_();
} else {
this->pin_->digital_write(false);
}
if (this->enable_on_boot_)
this->request_high_power();
}
@@ -47,6 +52,10 @@ void PowerSupply::unrequest_high_power() {
return;
}
this->active_requests_--;
this->schedule_off_if_idle_();
}
void PowerSupply::schedule_off_if_idle_() {
if (this->active_requests_ == 0) {
this->set_timeout(POWER_OFF_TIMEOUT_ID, this->keep_on_time_, [this]() {
ESP_LOGV(TAG, "Disabling");
@@ -54,7 +63,10 @@ void PowerSupply::unrequest_high_power() {
});
}
}
void PowerSupply::on_powerdown() {
if (this->pin_->get_hold())
return;
this->active_requests_ = 0;
this->pin_->digital_write(false);
}
@@ -39,6 +39,7 @@ class PowerSupply final : public Component {
uint32_t keep_on_time_;
int16_t active_requests_{0}; // use signed integer to make catching negative requests easier.
bool enable_on_boot_{false};
void schedule_off_if_idle_();
};
class PowerSupplyRequester {
+1
View File
@@ -474,6 +474,7 @@
#define USE_USB_UART_CP210X
#define USE_USB_UART_FT23XX
#define USE_USB_UART_PL2303
#define USE_GPIO_HOLD
// USB identity on serial proxy ports needs the usb_host stack
#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
+9
View File
@@ -2,6 +2,7 @@
#include <algorithm>
#include <cstdint>
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -27,6 +28,7 @@ enum Flags : uint8_t {
FLAG_OPEN_DRAIN = 0x04,
FLAG_PULLUP = 0x08,
FLAG_PULLDOWN = 0x10,
FLAG_HOLD = 0x20,
};
class FlagsHelper {
@@ -79,6 +81,13 @@ class GPIOPin {
virtual size_t dump_summary(char *buffer, size_t len) const;
virtual bool is_internal() { return false; }
#ifdef USE_GPIO_HOLD
bool get_hold() const { return this->get_flags() & gpio::FLAG_HOLD; }
virtual bool is_held() const { return false; }
#else
bool get_hold() const { return false; }
bool is_held() const { return false; }
#endif
};
/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)