mirror of
https://github.com/esphome/esphome.git
synced 2026-10-11 05:44:15 +00:00
[gpio][esp32][uart] Add option to hold gpio during (deep)sleep and reboot (3/3) (#18964)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
J. Nick Koston
parent
dc553aae55
commit
ba7d3bd770
@@ -111,7 +111,7 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
|
||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
|
||||
#ifndef 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();
|
||||
|
||||
@@ -50,17 +50,52 @@ struct ISRPinArg {
|
||||
gpio_num_t pin;
|
||||
gpio::Flags flags;
|
||||
bool inverted;
|
||||
#ifdef USE_GPIO_HOLD
|
||||
bool hold;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
bool use_rtc;
|
||||
int rtc_pin;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef USE_GPIO_HOLD
|
||||
// Re-latch a held pad onto the values just written to its registers.
|
||||
static inline void refresh_hold(const ESP32InternalGPIOPin &pin) {
|
||||
pin.set_hold(false);
|
||||
pin.set_hold(true);
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR isr_refresh_hold(const ISRPinArg *arg) {
|
||||
if (!arg->hold)
|
||||
return;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
if (arg->use_rtc) {
|
||||
rtcio_hal_hold_disable(arg->rtc_pin);
|
||||
rtcio_hal_hold_enable(arg->rtc_pin);
|
||||
return;
|
||||
}
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
// GPIO0-21 are held from the RTC domain; the digital hold mask is zero for them
|
||||
if (arg->pin < SOC_RTCIO_PIN_COUNT) {
|
||||
rtcio_hal_hold_disable(arg->pin);
|
||||
rtcio_hal_hold_enable(arg->pin);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
gpio_hal_hold_dis(&GPIO_HAL, arg->pin);
|
||||
gpio_hal_hold_en(&GPIO_HAL, arg->pin);
|
||||
}
|
||||
#endif
|
||||
|
||||
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = this->get_pin_num();
|
||||
arg->flags = gpio::FLAG_NONE;
|
||||
arg->inverted = this->pin_flags_.inverted;
|
||||
#ifdef USE_GPIO_HOLD
|
||||
arg->hold = this->get_hold();
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
arg->use_rtc = rtc_gpio_is_valid_gpio(this->get_pin_num());
|
||||
if (arg->use_rtc)
|
||||
@@ -105,7 +140,23 @@ size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||
return snprintf(buffer, len, "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
|
||||
}
|
||||
|
||||
#ifdef USE_GPIO_HOLD
|
||||
void ESP32InternalGPIOPin::set_hold(bool hold) const {
|
||||
if (!this->get_hold())
|
||||
return;
|
||||
if (hold) {
|
||||
gpio_hold_en(this->get_pin_num());
|
||||
} else {
|
||||
gpio_hold_dis(this->get_pin_num());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ESP32InternalGPIOPin::setup() {
|
||||
#ifdef USE_GPIO_HOLD
|
||||
// Hold before gpio_config so the pad keeps its state while the registers change
|
||||
this->set_hold(true);
|
||||
#endif
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(this->pin_);
|
||||
conf.mode = flags_to_mode(this->flags_);
|
||||
@@ -116,6 +167,18 @@ void ESP32InternalGPIOPin::setup() {
|
||||
if (this->flags_ & gpio::FLAG_OUTPUT) {
|
||||
gpio_set_drive_capability(this->get_pin_num(), this->get_drive_strength());
|
||||
}
|
||||
#ifdef USE_GPIO_HOLD
|
||||
// gpio_hold_dis logs an error on input-only pads
|
||||
if (GPIO_IS_VALID_OUTPUT_GPIO(this->get_pin_num())) {
|
||||
if (!this->get_hold()) {
|
||||
// Release a hold left behind by an earlier boot
|
||||
gpio_hold_dis(this->get_pin_num());
|
||||
} else if (this->flags_ & gpio::FLAG_INPUT) {
|
||||
// Inputs apply the config now so reads work; outputs wait for the first write
|
||||
refresh_hold(*this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
@@ -130,6 +193,9 @@ void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
pull_mode = GPIO_PULLDOWN_ONLY;
|
||||
}
|
||||
gpio_set_pull_mode(this->get_pin_num(), pull_mode);
|
||||
#ifdef USE_GPIO_HOLD
|
||||
refresh_hold(*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ESP32InternalGPIOPin::digital_read() {
|
||||
@@ -137,6 +203,9 @@ bool ESP32InternalGPIOPin::digital_read() {
|
||||
}
|
||||
void ESP32InternalGPIOPin::digital_write(bool value) {
|
||||
gpio_set_level(this->get_pin_num(), value != this->pin_flags_.inverted ? 1 : 0);
|
||||
#ifdef USE_GPIO_HOLD
|
||||
refresh_hold(*this);
|
||||
#endif
|
||||
}
|
||||
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(this->get_pin_num()); }
|
||||
|
||||
@@ -144,6 +213,8 @@ void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(this->ge
|
||||
|
||||
using namespace esp32;
|
||||
|
||||
// NOLINTBEGIN(clang-analyzer-core.FixedAddressDereference) -- some gpio_hal functions use MMIO at fixed addresses
|
||||
// internally
|
||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
|
||||
return bool(gpio_hal_get_level(&GPIO_HAL, arg->pin)) != arg->inverted;
|
||||
@@ -152,6 +223,9 @@ bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
|
||||
gpio_hal_set_level(&GPIO_HAL, arg->pin, value != arg->inverted);
|
||||
#ifdef USE_GPIO_HOLD
|
||||
isr_refresh_hold(arg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
@@ -203,8 +277,12 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
gpio_hal_input_disable(&GPIO_HAL, arg->pin);
|
||||
}
|
||||
}
|
||||
#ifdef USE_GPIO_HOLD
|
||||
isr_refresh_hold(arg);
|
||||
#endif
|
||||
arg->flags = flags;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-core.FixedAddressDereference)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ class ESP32InternalGPIOPin final : public GPIOPin {
|
||||
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
|
||||
/// Apply or release the pad hold. Does nothing unless the pin is configured with hold_state.
|
||||
void set_hold(bool hold) const;
|
||||
bool is_held() const override {
|
||||
if (!this->get_hold())
|
||||
return false;
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Any
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
@@ -248,6 +249,7 @@ ESP32_PIN_SCHEMA = cv.All(
|
||||
cv.float_with_unit("current", "mA", optional_unit=True),
|
||||
cv.enum(DRIVE_STRENGTHS),
|
||||
),
|
||||
cv.Optional(CONF_HOLD_STATE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
validate_gpio_pin,
|
||||
@@ -266,7 +268,11 @@ async def esp32_pin_to_code(config):
|
||||
cg.add(var.set_inverted(True))
|
||||
if CONF_DRIVE_STRENGTH in config:
|
||||
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
flags = pins.gpio_flags_expr(config[CONF_MODE])
|
||||
if config[CONF_HOLD_STATE]:
|
||||
flags = flags | cg.gpio_Flags.FLAG_HOLD
|
||||
cg.add_define("USE_GPIO_HOLD")
|
||||
cg.add(var.set_flags(flags))
|
||||
return var
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Pin checks shared by the ESP32 variant validators."""
|
||||
|
||||
from logging import Logger
|
||||
from typing import Any
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.const import CONF_NUMBER
|
||||
|
||||
|
||||
def check_usb_jtag_pin(num: int, usb_jtag_pins: set[int], logger: Logger) -> None:
|
||||
if num in usb_jtag_pins:
|
||||
logger.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
num,
|
||||
)
|
||||
|
||||
|
||||
def check_usb_jtag_hold(
|
||||
conf: dict[str, Any], usb_jtag_pins: set[int], logger: Logger
|
||||
) -> None:
|
||||
num = conf[CONF_NUMBER]
|
||||
if conf.get(CONF_HOLD_STATE) and num in usb_jtag_pins:
|
||||
logger.warning(
|
||||
"GPIO%d cannot hold at low level during wakeup from deep sleep.", num
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
@@ -53,6 +54,7 @@ def esp32_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
is_hold = value.get(CONF_HOLD_STATE)
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
@@ -70,6 +72,14 @@ def esp32_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
if is_hold and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) is input-only and cannot be held.",
|
||||
[CONF_HOLD_STATE],
|
||||
)
|
||||
if is_hold and num == 20:
|
||||
# Not in the digital hold mask and not an RTC pad, so the driver cannot hold it
|
||||
raise cv.Invalid("GPIO20 has no hold function.", [CONF_HOLD_STATE])
|
||||
|
||||
check_strapping_pin(value, _ESP32_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,6 +5,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
_ESP32C3_SPI_PSRAM_PINS = {
|
||||
12: "SPIHD",
|
||||
13: "SPIWP",
|
||||
@@ -28,12 +30,7 @@ def esp32_c3_validate_gpio_pin(value: int) -> int:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP32C3_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32C3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
@@ -49,6 +46,7 @@ def esp32_c3_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32C3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C3_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,6 +5,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c5/include/hal/i2c_ll.h
|
||||
_ESP32C5_I2C_LP_PINS = {"SDA": 2, "SCL": 3}
|
||||
|
||||
@@ -20,6 +22,8 @@ _ESP32C5_SPI_PSRAM_PINS = {
|
||||
|
||||
_ESP32C5_STRAPPING_PINS = {2, 7, 27, 28}
|
||||
|
||||
_ESP32C5_USB_JTAG_PINS = {13, 14}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -31,6 +35,7 @@ def esp32_c5_validate_gpio_pin(value: int) -> int:
|
||||
f"This pin cannot be used on ESP32-C5s and is already used by the SPI/PSRAM interface (function: {_ESP32C5_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
|
||||
check_usb_jtag_pin(value, _ESP32C5_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -45,6 +50,8 @@ def esp32_c5_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32C5_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C5_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c6/include/hal/i2c_ll.h
|
||||
_ESP32C6_I2C_LP_PINS = {"SDA": 6, "SCL": 7}
|
||||
|
||||
@@ -32,12 +34,7 @@ def esp32_c6_validate_gpio_pin(value: int) -> int:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C6s and is already used by the SPI/PSRAM interface (function: {_ESP32C6_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP32C6_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32C6_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
@@ -52,6 +49,7 @@ def esp32_c6_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32C6_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C6_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,6 +5,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# GPIO14-17, GPIO19-21 are used for SPI flash/PSRAM
|
||||
_ESP32C61_SPI_PSRAM_PINS = {
|
||||
14: "SPICS0",
|
||||
@@ -18,6 +20,8 @@ _ESP32C61_SPI_PSRAM_PINS = {
|
||||
|
||||
_ESP32C61_STRAPPING_PINS = {8, 9}
|
||||
|
||||
_ESP32C61_USB_JTAG_PINS = {12, 13}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -29,6 +33,7 @@ def esp32_c61_validate_gpio_pin(value: int) -> int:
|
||||
f"This pin cannot be used on ESP32-C61s and is already used by the SPI/PSRAM interface (function: {_ESP32C61_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
|
||||
check_usb_jtag_pin(value, _ESP32C61_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -43,5 +48,7 @@ def esp32_c61_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
# All ESP32-C61 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32C61_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C61_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,6 +5,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
_ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21}
|
||||
|
||||
_ESP32H2_USB_JTAG_PINS = {26, 27}
|
||||
@@ -24,12 +26,7 @@ def esp32_h2_validate_gpio_pin(value: int) -> int:
|
||||
"See https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html",
|
||||
value,
|
||||
)
|
||||
if value in _ESP32H2_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32H2_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
@@ -44,5 +41,6 @@ def esp32_h2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32H2_USB_JTAG_PINS, _LOGGER)
|
||||
check_strapping_pin(value, _ESP32H2_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,17 +5,22 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# Partial set from the ESP-IDF / esptool boot-mode docs:
|
||||
# https://docs.espressif.com/projects/esptool/en/latest/esp32h21/advanced-topics/boot-mode-selection.html
|
||||
# The full list awaits the ESP32-H21 datasheet's "Strapping Pins" section.
|
||||
_ESP32H21_STRAPPING_PINS: set[int] = {13, 14}
|
||||
|
||||
_ESP32H21_USB_JTAG_PINS = {17, 18}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_h21_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 25:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-25)")
|
||||
check_usb_jtag_pin(value, _ESP32H21_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -30,5 +35,7 @@ def esp32_h21_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32H21_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32H21_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,17 +5,22 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# Partial set from the ESP-IDF / esptool boot-mode docs:
|
||||
# https://docs.espressif.com/projects/esptool/en/latest/esp32h4/advanced-topics/boot-mode-selection.html
|
||||
# The full list awaits the ESP32-H4 datasheet's "Strapping Pins" section.
|
||||
_ESP32H4_STRAPPING_PINS: set[int] = {13, 14}
|
||||
|
||||
_ESP32H4_USB_JTAG_PINS = {13, 14}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_h4_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
check_usb_jtag_pin(value, _ESP32H4_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -30,5 +35,7 @@ def esp32_h4_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32H4_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32H4_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,6 +5,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_datasheet_en.pdf
|
||||
_ESP32P4_LP_PINS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
|
||||
|
||||
@@ -18,12 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
def esp32_p4_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 54:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)")
|
||||
if value in _ESP32P4_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32P4_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
@@ -38,6 +35,7 @@ def esp32_p4_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32P4_USB_JTAG_PINS, _LOGGER)
|
||||
check_strapping_pin(value, _ESP32P4_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
@@ -70,6 +71,11 @@ def esp32_s2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
if value.get(CONF_HOLD_STATE) and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} is input-only and cannot be held.",
|
||||
[CONF_HOLD_STATE],
|
||||
)
|
||||
|
||||
check_strapping_pin(value, _ESP32S2_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -12,6 +12,8 @@ from esphome.const import (
|
||||
from esphome.pins import PIN_SCHEMA_REGISTRY, check_strapping_pin
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
_ESP32S3_SPI_PSRAM_PINS = {
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
@@ -53,12 +55,7 @@ def esp32_s3_validate_gpio_pin(value: int) -> int:
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.")
|
||||
if value in _ESP32S3_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32S3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
@@ -73,6 +70,7 @@ def esp32_s3_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32S3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32S3_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -5,6 +5,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# Per the ESP32-S31 IDF DOCS and datasheet:
|
||||
# https://docs.espressif.com/projects/esp-idf/en/v6.1/esp32s31/api-reference/peripherals/gpio.html
|
||||
# https://documentation.espressif.com/esp32-s31_datasheet_en.pdf
|
||||
@@ -16,6 +18,8 @@ _ESP32S31_STRAPPING_PINS: set[int] = {36, 37, 60, 61}
|
||||
# LP I2C is fixed to GPIO6 (SCL) / GPIO7 (SDA) per the datasheet IO MUX table.
|
||||
_ESP32S31_I2C_LP_PINS = {"SDA": 7, "SCL": 6}
|
||||
|
||||
_ESP32S31_USB_JTAG_PINS = {33, 34}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -28,6 +32,7 @@ def esp32_s31_validate_gpio_pin(value: int) -> int:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{value} is reserved for the SPI flash interface on ESP32-S31 and cannot be used."
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32S31_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -46,6 +51,8 @@ def esp32_s31_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32S31_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32S31_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
@@ -238,6 +238,10 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#ifdef USE_GPIO_HOLD
|
||||
// Release held pins so the UART peripheral can drive them
|
||||
this->set_pins_hold_(false);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UART_WAKE_LOOP_ON_RX
|
||||
// Register ISR callback to wake the main loop when UART data arrives.
|
||||
@@ -514,7 +518,20 @@ void IDFUARTComponent::on_shutdown() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
#ifdef USE_GPIO_HOLD
|
||||
// Hold the pins again so they keep their state through the reset
|
||||
this->set_pins_hold_(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_GPIO_HOLD
|
||||
void IDFUARTComponent::set_pins_hold_(bool hold) {
|
||||
for (auto *pin : {this->tx_pin_, this->rx_pin_, this->flow_control_pin_}) {
|
||||
if (pin != nullptr)
|
||||
pin->set_hold(hold);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::uart
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -90,6 +90,9 @@ class IDFUARTComponent final : public UARTComponent, public Component {
|
||||
|
||||
protected:
|
||||
void check_logger_conflict() override;
|
||||
#ifdef USE_GPIO_HOLD
|
||||
void set_pins_hold_(bool hold);
|
||||
#endif
|
||||
uint32_t line_inversion_mask_();
|
||||
// Re-applies what uart_param_config() resets: inversion, RX threshold/timeout, mode.
|
||||
esp_err_t apply_line_settings_();
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.components.esp32 import (
|
||||
_ESP_TLS_LINKING_COMPONENTS,
|
||||
DEFAULT_EXCLUDED_IDF_COMPONENTS,
|
||||
@@ -38,13 +39,30 @@ from esphome.components.esp32.const import (
|
||||
KEY_NETWORK_SDKCONFIG,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32C61,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32H4,
|
||||
VARIANT_ESP32H21,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32S31,
|
||||
)
|
||||
from esphome.components.esp32.gpio import validate_gpio_pin
|
||||
from esphome.components.esp32.gpio import validate_gpio_pin, validate_supports
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_IGNORE_PIN_VALIDATION_ERROR,
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
PlatformFramework,
|
||||
Toolchain,
|
||||
)
|
||||
@@ -57,7 +75,7 @@ def test_esp32_config(
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA, VARIANT_ESP32, VARIANT_FRIENDLY
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA, VARIANT_FRIENDLY
|
||||
|
||||
# Example ESP32 configuration
|
||||
config = {
|
||||
@@ -1722,9 +1740,8 @@ def test_esp32_s31_gpio_validation(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""S31: GPIO26-28/30-32 are reserved for the SPI flash interface, GPIO29
|
||||
and GPIO41 do not exist, GPIO33 is a normal pin, and GPIO36 is a
|
||||
and GPIO41 do not exist, GPIO35 is a normal pin, and GPIO36 is a
|
||||
strapping pin."""
|
||||
from esphome.components.esp32.const import VARIANT_ESP32S31
|
||||
from esphome.components.esp32.gpio import validate_supports
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_OPEN_DRAIN, CONF_OUTPUT
|
||||
|
||||
@@ -1735,8 +1752,8 @@ def test_esp32_s31_gpio_validation(
|
||||
input_mode = {CONF_INPUT: True, CONF_OUTPUT: False, CONF_OPEN_DRAIN: False}
|
||||
|
||||
# Not reserved; a normal GPIO
|
||||
pin = {CONF_NUMBER: 33, CONF_IGNORE_PIN_VALIDATION_ERROR: False}
|
||||
assert validate_gpio_pin(pin)[CONF_NUMBER] == 33
|
||||
pin = {CONF_NUMBER: 35, CONF_IGNORE_PIN_VALIDATION_ERROR: False}
|
||||
assert validate_gpio_pin(pin)[CONF_NUMBER] == 35
|
||||
|
||||
# Reserved for the SPI flash interface, but can be bypassed with
|
||||
# ignore_pin_validation_error
|
||||
@@ -1764,6 +1781,113 @@ def test_esp32_s31_gpio_validation(
|
||||
assert "GPIO36 is a strapping pin" in caplog.text
|
||||
|
||||
|
||||
_INPUT_ONLY_SETTINGS = (
|
||||
(CONF_OUTPUT, "does not support output pin mode"),
|
||||
(CONF_PULLUP, "does not support pullups"),
|
||||
(CONF_PULLDOWN, "does not support pulldowns"),
|
||||
(CONF_HOLD_STATE, "is input-only and cannot be held"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "number", "setting", "error"),
|
||||
[
|
||||
pytest.param(
|
||||
variant, number, setting, error, id=f"{name}-gpio{number}-{setting}"
|
||||
)
|
||||
for variant, name, numbers in (
|
||||
(VARIANT_ESP32, "esp32", range(34, 40)),
|
||||
(VARIANT_ESP32S2, "s2", (46,)),
|
||||
)
|
||||
for number in numbers
|
||||
for setting, error in _INPUT_ONLY_SETTINGS
|
||||
]
|
||||
+ [
|
||||
pytest.param(
|
||||
VARIANT_ESP32,
|
||||
20,
|
||||
CONF_HOLD_STATE,
|
||||
"GPIO20 has no hold function",
|
||||
id="esp32-gpio20-hold_state",
|
||||
)
|
||||
],
|
||||
)
|
||||
def test_input_only_gpio_rejects_unsupported_modes(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
variant: str,
|
||||
number: int,
|
||||
setting: str,
|
||||
error: str,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF, platform_data={KEY_VARIANT: variant})
|
||||
mode = {
|
||||
CONF_INPUT: True,
|
||||
CONF_OUTPUT: False,
|
||||
CONF_OPEN_DRAIN: False,
|
||||
CONF_PULLUP: False,
|
||||
CONF_PULLDOWN: False,
|
||||
}
|
||||
pin = {CONF_NUMBER: number, CONF_MODE: mode}
|
||||
if setting == CONF_HOLD_STATE:
|
||||
pin[setting] = True
|
||||
else:
|
||||
mode[setting] = True
|
||||
|
||||
with pytest.raises(cv.Invalid, match=error):
|
||||
validate_supports(pin)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "number"),
|
||||
[
|
||||
pytest.param(VARIANT_ESP32C3, 18, id="c3-18"),
|
||||
pytest.param(VARIANT_ESP32C3, 19, id="c3-19"),
|
||||
pytest.param(VARIANT_ESP32C5, 13, id="c5-13"),
|
||||
pytest.param(VARIANT_ESP32C5, 14, id="c5-14"),
|
||||
pytest.param(VARIANT_ESP32C6, 12, id="c6-12"),
|
||||
pytest.param(VARIANT_ESP32C6, 13, id="c6-13"),
|
||||
pytest.param(VARIANT_ESP32C61, 12, id="c61-12"),
|
||||
pytest.param(VARIANT_ESP32C61, 13, id="c61-13"),
|
||||
pytest.param(VARIANT_ESP32H2, 26, id="h2-26"),
|
||||
pytest.param(VARIANT_ESP32H2, 27, id="h2-27"),
|
||||
pytest.param(VARIANT_ESP32H4, 13, id="h4-13"),
|
||||
pytest.param(VARIANT_ESP32H4, 14, id="h4-14"),
|
||||
pytest.param(VARIANT_ESP32H21, 17, id="h21-17"),
|
||||
pytest.param(VARIANT_ESP32H21, 18, id="h21-18"),
|
||||
pytest.param(VARIANT_ESP32P4, 24, id="p4-24"),
|
||||
pytest.param(VARIANT_ESP32P4, 25, id="p4-25"),
|
||||
pytest.param(VARIANT_ESP32S3, 19, id="s3-19"),
|
||||
pytest.param(VARIANT_ESP32S3, 20, id="s3-20"),
|
||||
pytest.param(VARIANT_ESP32S31, 33, id="s31-33"),
|
||||
pytest.param(VARIANT_ESP32S31, 34, id="s31-34"),
|
||||
],
|
||||
)
|
||||
def test_usb_jtag_gpio_hold_state_warns(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
variant: str,
|
||||
number: int,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF, platform_data={KEY_VARIANT: variant})
|
||||
pin = {
|
||||
CONF_NUMBER: number,
|
||||
CONF_MODE: {
|
||||
CONF_INPUT: True,
|
||||
CONF_OUTPUT: False,
|
||||
CONF_OPEN_DRAIN: False,
|
||||
},
|
||||
CONF_HOLD_STATE: True,
|
||||
}
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
validate_supports(pin)
|
||||
|
||||
assert (
|
||||
f"GPIO{number} cannot hold at low level during wakeup from deep sleep."
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
_TLS_SERVER_OPTIONS = (
|
||||
"CONFIG_MBEDTLS_TLS_CLIENT_ONLY",
|
||||
"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT",
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Tests for GPIO output final validation."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.components.gpio.output import FINAL_VALIDATE_SCHEMA
|
||||
from esphome.config import Config
|
||||
from esphome.const import CONF_ID, CONF_PIN, CONF_POWER_SUPPLY, PlatformFramework
|
||||
from esphome.core import ID
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
|
||||
@pytest.mark.parametrize("power_supply_holds", [False, True])
|
||||
def test_output_hold_state_requires_power_supply_hold_state(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
power_supply_holds: bool,
|
||||
) -> None:
|
||||
supply_id = ID("supply", is_declaration=True, type="power_supply")
|
||||
full_config = Config()
|
||||
full_config["power_supply"] = [
|
||||
{CONF_ID: supply_id, CONF_PIN: {CONF_HOLD_STATE: power_supply_holds}}
|
||||
]
|
||||
full_config.declare_ids.append((supply_id, ["power_supply", 0, CONF_ID]))
|
||||
set_core_config(PlatformFramework.ESP32_IDF, full_config=full_config)
|
||||
|
||||
config = {
|
||||
CONF_PIN: {CONF_HOLD_STATE: True},
|
||||
CONF_POWER_SUPPLY: ID("supply", is_declaration=False, type="power_supply"),
|
||||
}
|
||||
if power_supply_holds:
|
||||
FINAL_VALIDATE_SCHEMA(config)
|
||||
else:
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match="The power supply pin must also set hold_state when an output it powers sets it",
|
||||
):
|
||||
FINAL_VALIDATE_SCHEMA(config)
|
||||
@@ -1,5 +1,27 @@
|
||||
substitutions:
|
||||
wakeup_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
<<: !include common-esp32-all.yaml
|
||||
packages:
|
||||
deep_sleep: !include common.yaml
|
||||
deep_sleep_esp32: !include common-esp32-all.yaml
|
||||
|
||||
power_supply:
|
||||
- id: power_supply_ds_esp32
|
||||
pin:
|
||||
number: GPIO5
|
||||
hold_state: true
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO22
|
||||
hold_state: true
|
||||
id: gpio_output_ds_esp32
|
||||
power_supply: power_supply_ds_esp32
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO23
|
||||
hold_state: true
|
||||
id: gpio_switch_ds_esp32
|
||||
|
||||
@@ -6,4 +6,19 @@ substitutions:
|
||||
switch_pin_3: GPIO6
|
||||
switch_pin_4: GPIO7
|
||||
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
gpio: !include common.yaml
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO8
|
||||
hold_state: true
|
||||
id: gpio_binary_sensor_esp32
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO9
|
||||
hold_state: true
|
||||
id: gpio_output_esp32
|
||||
|
||||
@@ -6,4 +6,26 @@ substitutions:
|
||||
switch_pin_3: GPIO16
|
||||
switch_pin_4: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
||||
packages:
|
||||
gpio: !include common.yaml
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO18
|
||||
hold_state: true
|
||||
id: gpio_binary_sensor_esp32
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO19
|
||||
hold_state: true
|
||||
id: gpio_output_esp32
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO21
|
||||
hold_state: true
|
||||
id: gpio_switch_esp32
|
||||
|
||||
@@ -14,7 +14,12 @@ esphome:
|
||||
|
||||
uart:
|
||||
- id: uart_id
|
||||
tx_pin: 17
|
||||
tx_pin:
|
||||
number: 17
|
||||
mode:
|
||||
pullup: true
|
||||
output: true
|
||||
hold_state: true
|
||||
rx_pin: 16
|
||||
flow_control_pin: 4
|
||||
baud_rate: 9600
|
||||
|
||||
Reference in New Issue
Block a user