[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:
luar123
2026-10-10 02:37:46 +00:00
committed by GitHub
co-authored by J. Nick Koston J. Nick Koston
parent dc553aae55
commit ba7d3bd770
25 changed files with 442 additions and 43 deletions
+130 -6
View File
@@ -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",