[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",
@@ -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
+16 -1
View File
@@ -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
+23 -1
View File
@@ -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
+6 -1
View File
@@ -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