mirror of
https://github.com/esphome/esphome.git
synced 2026-10-25 23:42:15 +00:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ec65b0cd6 | ||
|
|
daeab6987f | ||
|
|
84a48c5386 | ||
|
|
96a3ac823a | ||
|
|
7a5ace9c21 | ||
|
|
fb48063230 | ||
|
|
977d92b0be | ||
|
|
4db3f0407d | ||
|
|
d7bacd724d | ||
|
|
16e708fa4f | ||
|
|
7e226093a5 | ||
|
|
cdd35fdc6c | ||
|
|
ad87b2fef0 | ||
|
|
3956a5b7ad |
No files matched your search
@@ -1,7 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
@@ -41,23 +39,20 @@ from .const import (
|
||||
VARIANT_ESP32S31,
|
||||
esp32_ns,
|
||||
)
|
||||
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
|
||||
from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_supports
|
||||
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
|
||||
from .gpio_esp32_c5 import esp32_c5_validate_gpio_pin, esp32_c5_validate_supports
|
||||
from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports
|
||||
from .gpio_esp32_c61 import esp32_c61_validate_gpio_pin, esp32_c61_validate_supports
|
||||
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
|
||||
from .gpio_esp32_h4 import esp32_h4_validate_gpio_pin, esp32_h4_validate_supports
|
||||
from .gpio_esp32_h21 import esp32_h21_validate_gpio_pin, esp32_h21_validate_supports
|
||||
from .gpio_esp32_p4 import esp32_p4_validate_gpio_pin, esp32_p4_validate_supports
|
||||
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
|
||||
from .gpio_esp32_s3 import (
|
||||
esp32_s3_final_validate_pins,
|
||||
esp32_s3_validate_gpio_pin,
|
||||
esp32_s3_validate_supports,
|
||||
)
|
||||
from .gpio_esp32_s31 import esp32_s31_validate_gpio_pin, esp32_s31_validate_supports
|
||||
from .gpio_common import ESP32PinSpec
|
||||
from .gpio_esp32 import ESP32_PIN_SPEC
|
||||
from .gpio_esp32_c2 import ESP32_C2_PIN_SPEC
|
||||
from .gpio_esp32_c3 import ESP32_C3_PIN_SPEC
|
||||
from .gpio_esp32_c5 import ESP32_C5_PIN_SPEC
|
||||
from .gpio_esp32_c6 import ESP32_C6_PIN_SPEC
|
||||
from .gpio_esp32_c61 import ESP32_C61_PIN_SPEC
|
||||
from .gpio_esp32_h2 import ESP32_H2_PIN_SPEC
|
||||
from .gpio_esp32_h4 import ESP32_H4_PIN_SPEC
|
||||
from .gpio_esp32_h21 import ESP32_H21_PIN_SPEC
|
||||
from .gpio_esp32_p4 import ESP32_P4_PIN_SPEC
|
||||
from .gpio_esp32_s2 import ESP32_S2_PIN_SPEC
|
||||
from .gpio_esp32_s3 import ESP32_S3_PIN_SPEC, esp32_s3_final_validate_pins
|
||||
from .gpio_esp32_s31 import ESP32_S31_PIN_SPEC
|
||||
|
||||
ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin)
|
||||
|
||||
@@ -98,66 +93,23 @@ def _translate_pin(value):
|
||||
return _lookup_pin(value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ESP32ValidationFunctions:
|
||||
pin_validation: Callable[[int], int]
|
||||
usage_validation: Callable[[dict[str, Any]], dict[str, Any]]
|
||||
final_validate: Callable[[ConfigType], None] | None = None
|
||||
|
||||
|
||||
_esp32_validations = {
|
||||
VARIANT_ESP32: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
|
||||
),
|
||||
VARIANT_ESP32C2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c2_validate_gpio_pin,
|
||||
usage_validation=esp32_c2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C3: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c3_validate_gpio_pin,
|
||||
usage_validation=esp32_c3_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C5: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c5_validate_gpio_pin,
|
||||
usage_validation=esp32_c5_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C6: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c6_validate_gpio_pin,
|
||||
usage_validation=esp32_c6_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C61: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c61_validate_gpio_pin,
|
||||
usage_validation=esp32_c61_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32H2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_h2_validate_gpio_pin,
|
||||
usage_validation=esp32_h2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32H4: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_h4_validate_gpio_pin,
|
||||
usage_validation=esp32_h4_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32H21: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_h21_validate_gpio_pin,
|
||||
usage_validation=esp32_h21_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32P4: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_p4_validate_gpio_pin,
|
||||
usage_validation=esp32_p4_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32S2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s2_validate_gpio_pin,
|
||||
usage_validation=esp32_s2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32S3: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s3_validate_gpio_pin,
|
||||
usage_validation=esp32_s3_validate_supports,
|
||||
final_validate=esp32_s3_final_validate_pins,
|
||||
),
|
||||
VARIANT_ESP32S31: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s31_validate_gpio_pin,
|
||||
usage_validation=esp32_s31_validate_supports,
|
||||
),
|
||||
_esp32_pin_specs: dict[str, ESP32PinSpec] = {
|
||||
VARIANT_ESP32: ESP32_PIN_SPEC,
|
||||
VARIANT_ESP32C2: ESP32_C2_PIN_SPEC,
|
||||
VARIANT_ESP32C3: ESP32_C3_PIN_SPEC,
|
||||
VARIANT_ESP32C5: ESP32_C5_PIN_SPEC,
|
||||
VARIANT_ESP32C6: ESP32_C6_PIN_SPEC,
|
||||
VARIANT_ESP32C61: ESP32_C61_PIN_SPEC,
|
||||
VARIANT_ESP32H2: ESP32_H2_PIN_SPEC,
|
||||
VARIANT_ESP32H4: ESP32_H4_PIN_SPEC,
|
||||
VARIANT_ESP32H21: ESP32_H21_PIN_SPEC,
|
||||
VARIANT_ESP32P4: ESP32_P4_PIN_SPEC,
|
||||
VARIANT_ESP32S2: ESP32_S2_PIN_SPEC,
|
||||
VARIANT_ESP32S3: ESP32_S3_PIN_SPEC,
|
||||
VARIANT_ESP32S31: ESP32_S31_PIN_SPEC,
|
||||
}
|
||||
_final_validations: dict[str, Callable[[ConfigType], None]] = {
|
||||
VARIANT_ESP32S3: esp32_s3_final_validate_pins,
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +126,7 @@ def gpio_pin_number_validator(value):
|
||||
return value
|
||||
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
if variant not in _esp32_pin_specs:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
||||
return value
|
||||
@@ -182,12 +134,12 @@ def gpio_pin_number_validator(value):
|
||||
|
||||
def validate_gpio_pin(pin):
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
if variant not in _esp32_pin_specs:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
||||
ignore_pin_validation_warning = pin[CONF_IGNORE_PIN_VALIDATION_ERROR]
|
||||
try:
|
||||
pin[CONF_NUMBER] = _esp32_validations[variant].pin_validation(pin[CONF_NUMBER])
|
||||
pin[CONF_NUMBER] = _esp32_pin_specs[variant].validate_gpio_pin(pin[CONF_NUMBER])
|
||||
except cv.Invalid as exc:
|
||||
if not ignore_pin_validation_warning:
|
||||
raise
|
||||
@@ -198,10 +150,10 @@ def validate_gpio_pin(pin):
|
||||
exc,
|
||||
)
|
||||
else:
|
||||
# `ignore_pin_validation_error` only suppresses an error raised by the
|
||||
# variant's pin_validation above (e.g. SPI flash/PSRAM pins, invalid pin
|
||||
# numbers). If that didn't raise, the option is a no-op -- warn so the
|
||||
# user can clean it up, but don't block the build.
|
||||
# `ignore_pin_validation_error` only suppresses an error raised by
|
||||
# validate_gpio_pin above (e.g. SPI flash/PSRAM pins); pin numbers out of
|
||||
# range still fail validate_supports. If nothing raised, the option is a
|
||||
# no-op -- warn so the user can clean it up, but don't block the build.
|
||||
if ignore_pin_validation_warning:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d has no validation errors to ignore; "
|
||||
@@ -217,7 +169,7 @@ def validate_supports(value):
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
if variant not in _esp32_pin_specs:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
||||
if is_open_drain and not is_output:
|
||||
@@ -225,7 +177,7 @@ def validate_supports(value):
|
||||
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
|
||||
)
|
||||
|
||||
return _esp32_validations[variant].usage_validation(value)
|
||||
return _esp32_pin_specs[variant].validate_supports(value)
|
||||
|
||||
|
||||
# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-reference/peripherals/gpio.html#_CPPv416gpio_drive_cap_t
|
||||
@@ -276,6 +228,5 @@ async def esp32_pin_to_code(config):
|
||||
|
||||
def final_validate_pins(full_config: ConfigType) -> None:
|
||||
"""Run the active variant's pin final-validation, if it defines one."""
|
||||
funcs = _esp32_validations.get(CORE.data[KEY_ESP32][KEY_VARIANT])
|
||||
if funcs is not None and funcs.final_validate is not None:
|
||||
funcs.final_validate(full_config)
|
||||
if (final := _final_validations.get(CORE.data[KEY_ESP32][KEY_VARIANT])) is not None:
|
||||
final(full_config)
|
||||
@@ -1,26 +1,129 @@
|
||||
"""Pin checks shared by the ESP32 variant validators."""
|
||||
|
||||
from logging import Logger
|
||||
from typing import Any
|
||||
from collections.abc import Iterable, Mapping
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.const import CONF_NUMBER
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.pins import check_strapping_pin
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_NO_PINS: Mapping[int, str] = MappingProxyType({})
|
||||
|
||||
_INPUT_ONLY_REJECTED_MODES = (
|
||||
(CONF_OUTPUT, "does not support output pin mode."),
|
||||
(CONF_PULLUP, "does not support pullups."),
|
||||
(CONF_PULLDOWN, "does not support pulldowns."),
|
||||
)
|
||||
|
||||
|
||||
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 spi_psram_errors(variant: str, functions: Mapping[int, str]) -> dict[int, str]:
|
||||
"""Error messages for pins taken by the SPI flash/PSRAM interface."""
|
||||
return {
|
||||
num: f"This pin cannot be used on {variant}s and is already used by the"
|
||||
f" SPI/PSRAM interface (function: {function})"
|
||||
for num, function in functions.items()
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
def unusable_errors(variant: str, pins: Iterable[int]) -> dict[int, str]:
|
||||
"""Error messages for pins that are missing from the GPIO matrix."""
|
||||
return {num: f"The pin GPIO{num} is not usable on {variant}s." for num in pins}
|
||||
|
||||
|
||||
class ESP32PinSpec:
|
||||
"""Pin facts of one ESP32 variant, with the two validators built from them.
|
||||
|
||||
`error_pins` fail pin validation, which `ignore_pin_validation_error` bypasses;
|
||||
`fatal_pins` and pins above `max_pin` fail both validators, so the option cannot bypass them.
|
||||
`input_only_label` is inserted after "GPIOn" in the input-only errors.
|
||||
A plain class, not a dataclass: building a dataclass costs ~0.5 ms of import.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"error_pins",
|
||||
"fatal_pins",
|
||||
"input_only_label",
|
||||
"input_only_pins",
|
||||
"max_pin",
|
||||
"no_hold_pins",
|
||||
"strapping_pins",
|
||||
"usb_jtag_pins",
|
||||
"warning_pins",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
max_pin: int,
|
||||
strapping_pins: frozenset[int],
|
||||
usb_jtag_pins: frozenset[int] = frozenset(),
|
||||
error_pins: Mapping[int, str] = _NO_PINS,
|
||||
fatal_pins: Mapping[int, str] = _NO_PINS,
|
||||
warning_pins: Mapping[int, str] = _NO_PINS,
|
||||
input_only_pins: frozenset[int] = frozenset(),
|
||||
input_only_label: str = "",
|
||||
no_hold_pins: frozenset[int] = frozenset(),
|
||||
) -> None:
|
||||
self.max_pin = max_pin
|
||||
self.strapping_pins = strapping_pins
|
||||
self.usb_jtag_pins = usb_jtag_pins
|
||||
self.error_pins = error_pins
|
||||
self.fatal_pins = fatal_pins
|
||||
self.warning_pins = warning_pins
|
||||
self.input_only_pins = input_only_pins
|
||||
self.input_only_label = input_only_label
|
||||
self.no_hold_pins = no_hold_pins
|
||||
|
||||
def _check_pin_exists(self, num: int) -> None:
|
||||
if num < 0 or num > self.max_pin:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-{self.max_pin})")
|
||||
if (message := self.fatal_pins.get(num)) is not None:
|
||||
raise cv.Invalid(message)
|
||||
|
||||
def validate_gpio_pin(self, value: int) -> int:
|
||||
self._check_pin_exists(value)
|
||||
if (message := self.error_pins.get(value)) is not None:
|
||||
raise cv.Invalid(message)
|
||||
if (message := self.warning_pins.get(value)) is not None:
|
||||
_LOGGER.warning("%s", message)
|
||||
if value in self.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,
|
||||
)
|
||||
return value
|
||||
|
||||
def validate_supports(self, value: ConfigType) -> ConfigType:
|
||||
num = value[CONF_NUMBER]
|
||||
self._check_pin_exists(num)
|
||||
hold = value.get(CONF_HOLD_STATE)
|
||||
if num in self.input_only_pins:
|
||||
prefix = f"GPIO{num}{self.input_only_label}"
|
||||
mode = value[CONF_MODE]
|
||||
for key, problem in _INPUT_ONLY_REJECTED_MODES:
|
||||
if mode[key]:
|
||||
raise cv.Invalid(f"{prefix} {problem}", [CONF_MODE, key])
|
||||
if hold:
|
||||
raise cv.Invalid(
|
||||
f"{prefix} is input-only and cannot be held.", [CONF_HOLD_STATE]
|
||||
)
|
||||
if hold and num in self.no_hold_pins:
|
||||
raise cv.Invalid(f"GPIO{num} has no hold function.", [CONF_HOLD_STATE])
|
||||
if hold and num in self.usb_jtag_pins:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d cannot hold at low level during wakeup from deep sleep.", num
|
||||
)
|
||||
check_strapping_pin(value, self.strapping_pins, _LOGGER)
|
||||
return value
|
||||
@@ -1,17 +1,4 @@
|
||||
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,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.pins import check_strapping_pin
|
||||
from .gpio_common import ESP32PinSpec, unusable_errors
|
||||
|
||||
_ESP_SDIO_PINS = {
|
||||
6: "Flash Clock",
|
||||
@@ -20,66 +7,28 @@ _ESP_SDIO_PINS = {
|
||||
11: "Flash Command",
|
||||
}
|
||||
|
||||
_ESP32_STRAPPING_PINS = {0, 2, 5, 12, 15}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
if value in _ESP_SDIO_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32s and is already used by the flash interface"
|
||||
f" (function: {_ESP_SDIO_PINS[value]})."
|
||||
f" If you are using an ESP32 module that uses a different flash pin"
|
||||
f" configuration (e.g. ESP32-PICO-V3-02), you can set"
|
||||
f" 'ignore_pin_validation_error: true' to bypass this check."
|
||||
)
|
||||
if 9 <= value <= 10:
|
||||
_LOGGER.warning(
|
||||
"Pin %s (9-10) might already be used by the flash interface in QUAD IO flash mode.",
|
||||
value,
|
||||
)
|
||||
if value in (24, 28, 29, 30, 31):
|
||||
ESP32_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=39,
|
||||
strapping_pins=frozenset({0, 2, 5, 12, 15}),
|
||||
error_pins={
|
||||
**{
|
||||
num: "This pin cannot be used on ESP32s and is already used by the flash interface"
|
||||
f" (function: {function})."
|
||||
" If you are using an ESP32 module that uses a different flash pin"
|
||||
" configuration (e.g. ESP32-PICO-V3-02), you can set"
|
||||
" 'ignore_pin_validation_error: true' to bypass this check."
|
||||
for num, function in _ESP_SDIO_PINS.items()
|
||||
},
|
||||
# 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 ESP32s.")
|
||||
return value
|
||||
|
||||
|
||||
def esp32_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
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
|
||||
pass
|
||||
if is_output and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_pullup and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and 34 <= num <= 39:
|
||||
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
|
||||
**unusable_errors("ESP32", (24, 28, 29, 30, 31)),
|
||||
},
|
||||
warning_pins={
|
||||
num: f"Pin {num} (9-10) might already be used by the flash interface in QUAD IO flash mode."
|
||||
for num in (9, 10)
|
||||
},
|
||||
input_only_pins=frozenset(range(34, 40)),
|
||||
input_only_label=" (34-39)",
|
||||
# Not in the digital hold mask and not an RTC pad, so the driver cannot hold it
|
||||
no_hold_pins=frozenset({20}),
|
||||
)
|
||||
@@ -1,33 +1,3 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
_ESP32C2_STRAPPING_PINS = {8, 9}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c2_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 20:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 20:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-20)")
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_strapping_pin(value, _ESP32C2_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_C2_PIN_SPEC = ESP32PinSpec(max_pin=20, strapping_pins=frozenset({8, 9}))
|
||||
@@ -1,52 +1,18 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
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",
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
}
|
||||
|
||||
_ESP32C3_USB_JTAG_PINS = {18, 19}
|
||||
|
||||
_ESP32C3_STRAPPING_PINS = {2, 8, 9}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c3_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 21:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
|
||||
if value in _ESP32C3_SPI_PSRAM_PINS:
|
||||
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]})"
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32C3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c3_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 21:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-21)")
|
||||
|
||||
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
|
||||
ESP32_C3_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=21,
|
||||
strapping_pins=frozenset({2, 8, 9}),
|
||||
usb_jtag_pins=frozenset({18, 19}),
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C3",
|
||||
{
|
||||
12: "SPIHD",
|
||||
13: "SPIWP",
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -1,59 +1,28 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
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 esphome.const import CONF_SCL, CONF_SDA
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
# 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}
|
||||
|
||||
_ESP32C5_SPI_PSRAM_PINS = {
|
||||
16: "SPICS0",
|
||||
17: "SPIQ",
|
||||
18: "SPIWP",
|
||||
19: "VDD_SPI",
|
||||
20: "SPIHD",
|
||||
21: "SPICLK",
|
||||
22: "SPID",
|
||||
}
|
||||
|
||||
_ESP32C5_STRAPPING_PINS = {2, 7, 27, 28}
|
||||
|
||||
_ESP32C5_USB_JTAG_PINS = {13, 14}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c5_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 28:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-28)")
|
||||
if value in _ESP32C5_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
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
|
||||
|
||||
|
||||
def esp32_c5_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 28:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-28)")
|
||||
if is_input:
|
||||
# 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
|
||||
ESP32_C5_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=28,
|
||||
strapping_pins=frozenset({2, 7, 27, 28}),
|
||||
usb_jtag_pins=frozenset({13, 14}),
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C5",
|
||||
{
|
||||
16: "SPICS0",
|
||||
17: "SPIQ",
|
||||
18: "SPIWP",
|
||||
19: "VDD_SPI",
|
||||
20: "SPIHD",
|
||||
21: "SPICLK",
|
||||
22: "SPID",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def esp32_c5_validate_lp_i2c(value):
|
||||
|
||||
@@ -1,58 +1,28 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
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 esphome.const import CONF_SCL, CONF_SDA
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
# 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}
|
||||
|
||||
_ESP32C6_SPI_PSRAM_PINS = {
|
||||
24: "SPICS0",
|
||||
25: "SPIQ",
|
||||
26: "SPIWP",
|
||||
27: "VDD_SPI",
|
||||
28: "SPIHD",
|
||||
29: "SPICLK",
|
||||
30: "SPID",
|
||||
}
|
||||
|
||||
_ESP32C6_USB_JTAG_PINS = {12, 13}
|
||||
|
||||
_ESP32C6_STRAPPING_PINS = {4, 5, 8, 9, 15}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c6_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 30:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-30)")
|
||||
if value in _ESP32C6_SPI_PSRAM_PINS:
|
||||
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]})"
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32C6_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c6_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 30:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-30)")
|
||||
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
|
||||
ESP32_C6_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=30,
|
||||
strapping_pins=frozenset({4, 5, 8, 9, 15}),
|
||||
usb_jtag_pins=frozenset({12, 13}),
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C6",
|
||||
{
|
||||
24: "SPICS0",
|
||||
25: "SPIQ",
|
||||
26: "SPIWP",
|
||||
27: "VDD_SPI",
|
||||
28: "SPIHD",
|
||||
29: "SPICLK",
|
||||
30: "SPID",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def esp32_c6_validate_lp_i2c(value):
|
||||
|
||||
@@ -1,54 +1,20 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
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",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
19: "SPIWP",
|
||||
20: "SPIHD",
|
||||
21: "VDD_SPI",
|
||||
}
|
||||
|
||||
_ESP32C61_STRAPPING_PINS = {8, 9}
|
||||
|
||||
_ESP32C61_USB_JTAG_PINS = {12, 13}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c61_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 29:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-29)")
|
||||
if value in _ESP32C61_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
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
|
||||
|
||||
|
||||
def esp32_c61_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 29:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-29)")
|
||||
if is_input:
|
||||
# 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
|
||||
ESP32_C61_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=29,
|
||||
strapping_pins=frozenset({8, 9}),
|
||||
usb_jtag_pins=frozenset({12, 13}),
|
||||
# GPIO14-17, GPIO19-21 are used for SPI flash/PSRAM
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C61",
|
||||
{
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
19: "SPIWP",
|
||||
20: "SPIHD",
|
||||
21: "VDD_SPI",
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -1,46 +1,13 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
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}
|
||||
|
||||
_ESP32H2_STRAPPING_PINS = {8, 9, 25}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_h2_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 27:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-27)")
|
||||
if value in _ESP32H2_SPI_FLASH_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is reserved for SPI Flash communication on some ESP32-H2 chip variants.\n"
|
||||
"Utilizing SPI-reserved pins could cause unexpected failures.\n"
|
||||
"See https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html",
|
||||
value,
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32H2_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_h2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 27:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-27)")
|
||||
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
|
||||
ESP32_H2_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=27,
|
||||
strapping_pins=frozenset({8, 9, 25}),
|
||||
usb_jtag_pins=frozenset({26, 27}),
|
||||
warning_pins={
|
||||
num: f"GPIO{num} is reserved for SPI Flash communication on some ESP32-H2 chip variants.\n"
|
||||
"Utilizing SPI-reserved pins could cause unexpected failures.\n"
|
||||
"See https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html"
|
||||
for num in (6, 7, 15, 16, 17, 18, 19, 20, 21)
|
||||
},
|
||||
)
|
||||
@@ -1,41 +1,10 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
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
|
||||
|
||||
|
||||
def esp32_h21_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 25:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-25)")
|
||||
if is_input:
|
||||
# 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
|
||||
ESP32_H21_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=25,
|
||||
# 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.
|
||||
strapping_pins=frozenset({13, 14}),
|
||||
usb_jtag_pins=frozenset({17, 18}),
|
||||
)
|
||||
@@ -1,41 +1,10 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
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
|
||||
|
||||
|
||||
def esp32_h4_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-39)")
|
||||
if is_input:
|
||||
# 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
|
||||
ESP32_H4_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=39,
|
||||
# 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.
|
||||
strapping_pins=frozenset({13, 14}),
|
||||
usb_jtag_pins=frozenset({13, 14}),
|
||||
)
|
||||
@@ -1,43 +1,16 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
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 esphome.const import CONF_SCL, CONF_SDA
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
# 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}
|
||||
|
||||
_ESP32P4_USB_JTAG_PINS = {24, 25}
|
||||
|
||||
_ESP32P4_STRAPPING_PINS = {34, 35, 36, 37, 38}
|
||||
|
||||
_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)")
|
||||
check_usb_jtag_pin(value, _ESP32P4_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_p4_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 54:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-54)")
|
||||
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
|
||||
ESP32_P4_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=54,
|
||||
strapping_pins=frozenset({34, 35, 36, 37, 38}),
|
||||
usb_jtag_pins=frozenset({24, 25}),
|
||||
)
|
||||
|
||||
|
||||
def esp32_p4_validate_lp_i2c(value):
|
||||
|
||||
@@ -1,81 +1,30 @@
|
||||
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,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
_ESP32S2_SPI_PSRAM_PINS = {
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
28: "SPIWP",
|
||||
29: "SPICS0",
|
||||
30: "SPICLK",
|
||||
31: "SPIQ",
|
||||
32: "SPID",
|
||||
}
|
||||
|
||||
_ESP32S2_STRAPPING_PINS = {0, 45, 46}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors, unusable_errors
|
||||
|
||||
|
||||
def esp32_s2_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 46:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
|
||||
|
||||
if value in _ESP32S2_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
|
||||
if value in (22, 23, 24, 25):
|
||||
def s2_s3_error_pins(variant: str) -> dict[int, str]:
|
||||
"""Errors for the flash/PSRAM and missing pins, which S2 and S3 share."""
|
||||
return {
|
||||
**spi_psram_errors(
|
||||
variant,
|
||||
{
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
28: "SPIWP",
|
||||
29: "SPICS0",
|
||||
30: "SPICLK",
|
||||
31: "SPIQ",
|
||||
32: "SPID",
|
||||
},
|
||||
),
|
||||
# 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-S2s.")
|
||||
|
||||
return value
|
||||
**unusable_errors(variant, (22, 23, 24, 25)),
|
||||
}
|
||||
|
||||
|
||||
def esp32_s2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
|
||||
if num < 0 or num > 46:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
if is_output and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_pullup and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and num == 46:
|
||||
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
|
||||
ESP32_S2_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=46,
|
||||
strapping_pins=frozenset({0, 45, 46}),
|
||||
error_pins=s2_s3_error_pins("ESP32-S2"),
|
||||
input_only_pins=frozenset({46}),
|
||||
)
|
||||
@@ -1,28 +1,11 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DISABLED,
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
PLATFORM_ESP32,
|
||||
)
|
||||
from esphome.pins import PIN_SCHEMA_REGISTRY, check_strapping_pin
|
||||
from esphome.const import CONF_DISABLED, CONF_MODE, PLATFORM_ESP32
|
||||
from esphome.pins import PIN_SCHEMA_REGISTRY
|
||||
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",
|
||||
28: "SPIWP",
|
||||
29: "SPICS0",
|
||||
30: "SPICLK",
|
||||
31: "SPIQ",
|
||||
32: "SPID",
|
||||
}
|
||||
from .gpio_common import ESP32PinSpec
|
||||
from .gpio_esp32_s2 import s2_s3_error_pins
|
||||
|
||||
_ESP32S3R8_PSRAM_PINS = {
|
||||
33: "SPIIO4",
|
||||
@@ -32,48 +15,14 @@ _ESP32S3R8_PSRAM_PINS = {
|
||||
37: "SPIDQS",
|
||||
}
|
||||
|
||||
_ESP32S3_USB_JTAG_PINS = {19, 20}
|
||||
|
||||
_ESP32S3_STRAPPING_PINS = {0, 3, 45, 46}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_s3_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 48:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-48)")
|
||||
|
||||
if value in _ESP32S3_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP32S3_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
# GPIO33-37 (_ESP32S3R8_PSRAM_PINS) are only taken by the PSRAM interface in
|
||||
# octal mode -- whether that applies isn't known here, so the warning is
|
||||
# deferred to final_validate_pins() in gpio.py once the PSRAM mode is resolved.
|
||||
|
||||
if value in (22, 23, 24, 25):
|
||||
# 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.")
|
||||
check_usb_jtag_pin(value, _ESP32S3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_s3_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 48:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-48)")
|
||||
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
|
||||
ESP32_S3_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=48,
|
||||
strapping_pins=frozenset({0, 3, 45, 46}),
|
||||
usb_jtag_pins=frozenset({19, 20}),
|
||||
error_pins=s2_s3_error_pins("ESP32-S3"),
|
||||
)
|
||||
|
||||
|
||||
def esp32_s3_final_validate_pins(full_config: ConfigType) -> None:
|
||||
|
||||
@@ -1,60 +1,27 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
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 esphome.const import CONF_SCL, CONF_SDA
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
# LP I2C is fixed to GPIO6 (SCL) / GPIO7 (SDA) per the datasheet IO MUX table.
|
||||
_ESP32S31_I2C_LP_PINS = {"SDA": 7, "SCL": 6}
|
||||
|
||||
# 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
|
||||
_ESP32S31_SPI_FLASH_PINS: set[int] = {26, 27, 28, 30, 31, 32}
|
||||
_ESP32S31_INVALID_PINS: set[int] = {29, 41}
|
||||
# GPIO60/GPIO61 set the boot mode; GPIO37 selects the JTAG signal source;
|
||||
# GPIO36 sets the VDD_SPI voltage.
|
||||
_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__)
|
||||
|
||||
|
||||
def esp32_s31_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 61:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-61)")
|
||||
if value in _ESP32S31_INVALID_PINS:
|
||||
raise cv.Invalid(f"GPIO{value} does not exist on ESP32-S31.")
|
||||
if value in _ESP32S31_SPI_FLASH_PINS:
|
||||
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
|
||||
|
||||
|
||||
def esp32_s31_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 61:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-61)")
|
||||
# Checked here as well so ignore_pin_validation_error cannot bypass it;
|
||||
# these pins are not bonded and can never work
|
||||
if num in _ESP32S31_INVALID_PINS:
|
||||
raise cv.Invalid(f"GPIO{num} does not exist on ESP32-S31.")
|
||||
if is_input:
|
||||
# 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
|
||||
ESP32_S31_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=61,
|
||||
# GPIO60/GPIO61 set the boot mode; GPIO37 selects the JTAG signal source;
|
||||
# GPIO36 sets the VDD_SPI voltage.
|
||||
strapping_pins=frozenset({36, 37, 60, 61}),
|
||||
usb_jtag_pins=frozenset({33, 34}),
|
||||
error_pins={
|
||||
num: f"GPIO{num} is reserved for the SPI flash interface on ESP32-S31 and cannot be used."
|
||||
for num in (26, 27, 28, 30, 31, 32)
|
||||
},
|
||||
# Not bonded, so they can never work
|
||||
fatal_pins={num: f"GPIO{num} does not exist on ESP32-S31." for num in (29, 41)},
|
||||
)
|
||||
|
||||
|
||||
def esp32_s31_validate_lp_i2c(value):
|
||||
|
||||
@@ -15,32 +15,25 @@ ESPHOME_LOG_TAG(TAG, "http_request.ota");
|
||||
|
||||
void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
|
||||
|
||||
OtaHttpRequestComponent::Request &OtaHttpRequestComponent::get_request_() {
|
||||
if (this->request_ == nullptr)
|
||||
this->request_ = make_unique<Request>();
|
||||
return *this->request_;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->get_request_().md5_url.clear(); // URL was not valid; prevent flashing until it is
|
||||
this->md5_url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
Request &request = this->get_request_();
|
||||
request.md5_url = url;
|
||||
request.md5_expected.clear(); // to be retrieved later
|
||||
this->md5_url_ = url;
|
||||
this->md5_expected_.clear(); // to be retrieved later
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->get_request_().url.clear(); // URL was not valid; prevent flashing until it is
|
||||
this->url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
this->get_request_().url = url;
|
||||
this->url_ = url;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::flash() {
|
||||
if (this->request_ == nullptr || this->request_->url.empty()) {
|
||||
if (this->url_.empty()) {
|
||||
ESP_LOGE(TAG, "URL not set; cannot start update");
|
||||
return;
|
||||
}
|
||||
@@ -65,8 +58,8 @@ void OtaHttpRequestComponent::flash() {
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
this->notify_state_(ota::OTA_ERROR, 0.0f, ota_status);
|
||||
#endif
|
||||
this->request_->md5_computed.clear(); // will be reset at next attempt
|
||||
this->request_->md5_expected.clear(); // will be reset at next attempt
|
||||
this->md5_computed_.clear(); // will be reset at next attempt
|
||||
this->md5_expected_.clear(); // will be reset at next attempt
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -88,18 +81,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
md5::MD5Digest md5_receive;
|
||||
char md5_receive_str[33];
|
||||
|
||||
if (this->request_->md5_expected.empty() && !this->http_get_md5_()) {
|
||||
if (this->md5_expected_.empty() && !this->http_get_md5_()) {
|
||||
return OTA_MD5_INVALID;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "MD5 expected: %s", this->request_->md5_expected.c_str());
|
||||
ESP_LOGD(TAG, "MD5 expected: %s", this->md5_expected_.c_str());
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->request_->url);
|
||||
auto url_with_auth = this->get_url_with_auth_(this->url_);
|
||||
if (url_with_auth.empty()) {
|
||||
return OTA_BAD_URL;
|
||||
}
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->request_->url.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
|
||||
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
|
||||
@@ -186,9 +179,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
// verify MD5 is as expected and act accordingly
|
||||
md5_receive.calculate();
|
||||
md5_receive.get_hex(md5_receive_str);
|
||||
this->request_->md5_computed = md5_receive_str;
|
||||
if (strncmp(this->request_->md5_computed.c_str(), this->request_->md5_expected.c_str(), MD5_SIZE) != 0) {
|
||||
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->request_->md5_computed.c_str());
|
||||
this->md5_computed_ = md5_receive_str;
|
||||
if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
|
||||
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
|
||||
this->cleanup_(std::move(backend), container, /*abort_backend=*/true);
|
||||
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
} else {
|
||||
@@ -230,15 +223,11 @@ static std::string url_encode(const std::string &str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_password(const std::string &password) {
|
||||
this->get_request_().password = url_encode(password);
|
||||
}
|
||||
void OtaHttpRequestComponent::set_username(const std::string &username) {
|
||||
this->get_request_().username = url_encode(username);
|
||||
}
|
||||
void OtaHttpRequestComponent::set_password(const std::string &password) { this->password_ = url_encode(password); }
|
||||
void OtaHttpRequestComponent::set_username(const std::string &username) { this->username_ = url_encode(username); }
|
||||
|
||||
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
|
||||
if (this->request_->username.empty() || this->request_->password.empty()) {
|
||||
if (this->username_.empty() || this->password_.empty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -251,23 +240,23 @@ std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url)
|
||||
ESP_LOGD(TAG, "Using basic HTTP authentication");
|
||||
|
||||
start_char += 3; // skip '://' characters
|
||||
auto url_with_auth = url.substr(0, start_char) + this->request_->username + ":" + this->request_->password + "@" +
|
||||
url.substr(start_char);
|
||||
auto url_with_auth =
|
||||
url.substr(0, start_char) + this->username_ + ":" + this->password_ + "@" + url.substr(start_char);
|
||||
return url_with_auth;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::http_get_md5_() {
|
||||
if (this->request_->md5_url.empty()) {
|
||||
if (this->md5_url_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->request_->md5_url);
|
||||
auto url_with_auth = this->get_url_with_auth_(this->md5_url_);
|
||||
if (url_with_auth.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->request_->md5_url.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
if (container == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MD5 URL");
|
||||
@@ -284,8 +273,8 @@ bool OtaHttpRequestComponent::http_get_md5_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->request_->md5_expected.resize(MD5_SIZE);
|
||||
auto result = http_read_fully(container.get(), (uint8_t *) this->request_->md5_expected.data(), MD5_SIZE, MD5_SIZE,
|
||||
this->md5_expected_.resize(MD5_SIZE);
|
||||
auto result = http_read_fully(container.get(), (uint8_t *) this->md5_expected_.data(), MD5_SIZE, MD5_SIZE,
|
||||
this->parent_->get_timeout());
|
||||
container->end();
|
||||
|
||||
|
||||
@@ -27,35 +27,29 @@ class OtaHttpRequestComponent final : public ota::OTAComponent, public Parented<
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_md5_url(const std::string &md5_url);
|
||||
void set_md5(const std::string &md5) { this->get_request_().md5_expected = md5; }
|
||||
void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
|
||||
void set_password(const std::string &password);
|
||||
void set_url(const std::string &url);
|
||||
void set_username(const std::string &username);
|
||||
|
||||
std::string md5_computed() { return this->request_ != nullptr ? this->request_->md5_computed : std::string(); }
|
||||
std::string md5_expected() { return this->request_ != nullptr ? this->request_->md5_expected : std::string(); }
|
||||
std::string md5_computed() { return this->md5_computed_; }
|
||||
std::string md5_expected() { return this->md5_expected_; }
|
||||
|
||||
void flash();
|
||||
|
||||
protected:
|
||||
/// Allocated by the first setter, which only runs before a flash, and never freed, so a flash never loses it.
|
||||
struct Request {
|
||||
std::string md5_computed;
|
||||
std::string md5_expected;
|
||||
std::string md5_url;
|
||||
std::string password;
|
||||
std::string username;
|
||||
std::string url;
|
||||
};
|
||||
Request &get_request_();
|
||||
|
||||
void cleanup_(ota::OTABackendPtr backend, const std::shared_ptr<HttpContainer> &container, bool abort_backend);
|
||||
uint8_t do_ota_();
|
||||
std::string get_url_with_auth_(const std::string &url);
|
||||
bool http_get_md5_();
|
||||
bool validate_url_(const std::string &url);
|
||||
|
||||
std::unique_ptr<Request> request_;
|
||||
std::string md5_computed_{};
|
||||
std::string md5_expected_{};
|
||||
std::string md5_url_{};
|
||||
std::string password_{};
|
||||
std::string username_{};
|
||||
std::string url_{};
|
||||
int status_ = -1;
|
||||
static const uint16_t HTTP_RECV_BUFFER = 256; // the firmware GET chunk size
|
||||
};
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
"""Tests for the data driven ESP32 pin validators."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
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 _esp32_pin_specs
|
||||
from esphome.components.esp32.gpio_common import (
|
||||
ESP32PinSpec,
|
||||
spi_psram_errors,
|
||||
unusable_errors,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_IGNORE_STRAPPING_WARNING,
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
SPEC = ESP32PinSpec(
|
||||
max_pin=40,
|
||||
strapping_pins=frozenset({0, 3}),
|
||||
usb_jtag_pins=frozenset({3, 19}),
|
||||
error_pins={
|
||||
**spi_psram_errors("ESP32-X1", {26: "SPICS1"}),
|
||||
**unusable_errors("ESP32-X1", (22,)),
|
||||
},
|
||||
fatal_pins={29: "GPIO29 does not exist on ESP32-X1."},
|
||||
warning_pins={9: "GPIO9 might be used by flash."},
|
||||
input_only_pins=frozenset({34, 35}),
|
||||
input_only_label=" (34-35)",
|
||||
no_hold_pins=frozenset({20}),
|
||||
)
|
||||
|
||||
|
||||
def _conf(num: int, hold: bool = False, **mode: bool) -> ConfigType:
|
||||
return {
|
||||
CONF_NUMBER: num,
|
||||
CONF_MODE: {
|
||||
CONF_INPUT: False,
|
||||
CONF_OUTPUT: False,
|
||||
CONF_PULLUP: False,
|
||||
CONF_PULLDOWN: False,
|
||||
CONF_OPEN_DRAIN: False,
|
||||
**mode,
|
||||
},
|
||||
CONF_HOLD_STATE: hold,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("num", "message"),
|
||||
[
|
||||
(-1, "Invalid pin number: -1 (must be 0-40)"),
|
||||
(41, "Invalid pin number: 41 (must be 0-40)"),
|
||||
(29, "GPIO29 does not exist on ESP32-X1."),
|
||||
],
|
||||
)
|
||||
def test_rejected_by_both_validators(num: int, message: str) -> None:
|
||||
for validate, value in (
|
||||
(SPEC.validate_gpio_pin, num),
|
||||
(SPEC.validate_supports, _conf(num, input=True)),
|
||||
):
|
||||
with pytest.raises(cv.Invalid) as exc:
|
||||
validate(value)
|
||||
assert exc.value.msg == message
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("num", "message"),
|
||||
[
|
||||
(
|
||||
26,
|
||||
(
|
||||
"This pin cannot be used on ESP32-X1s and is already used by the"
|
||||
" SPI/PSRAM interface (function: SPICS1)"
|
||||
),
|
||||
),
|
||||
(22, "The pin GPIO22 is not usable on ESP32-X1s."),
|
||||
],
|
||||
)
|
||||
def test_error_pins_only_fail_pin_validation(num: int, message: str) -> None:
|
||||
with pytest.raises(cv.Invalid) as exc:
|
||||
SPEC.validate_gpio_pin(num)
|
||||
assert exc.value.msg == message
|
||||
SPEC.validate_supports(_conf(num, output=True))
|
||||
|
||||
|
||||
def test_warning_and_usb_jtag_pins(caplog: pytest.LogCaptureFixture) -> None:
|
||||
with caplog.at_level("WARNING"):
|
||||
assert SPEC.validate_gpio_pin(9) == 9
|
||||
assert SPEC.validate_gpio_pin(19) == 19
|
||||
assert SPEC.validate_gpio_pin(5) == 5
|
||||
assert caplog.messages == [
|
||||
"GPIO9 might be used by flash.",
|
||||
(
|
||||
"GPIO19 is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG."
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mode", "problem"),
|
||||
[
|
||||
(CONF_OUTPUT, "does not support output pin mode."),
|
||||
(CONF_PULLUP, "does not support pullups."),
|
||||
(CONF_PULLDOWN, "does not support pulldowns."),
|
||||
],
|
||||
)
|
||||
def test_input_only_pins_reject_modes(mode: str, problem: str) -> None:
|
||||
with pytest.raises(cv.Invalid) as exc:
|
||||
SPEC.validate_supports(_conf(35, input=True, **{mode: True}))
|
||||
assert exc.value.msg == f"GPIO35 (34-35) {problem}"
|
||||
assert exc.value.path == [CONF_MODE, mode]
|
||||
# The same modes are fine on a normal pin
|
||||
SPEC.validate_supports(_conf(5, input=True, **{mode: True}))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("num", "message"),
|
||||
[
|
||||
(34, "GPIO34 (34-35) is input-only and cannot be held."),
|
||||
(20, "GPIO20 has no hold function."),
|
||||
],
|
||||
)
|
||||
def test_pins_that_cannot_hold(num: int, message: str) -> None:
|
||||
SPEC.validate_supports(_conf(num, input=True))
|
||||
with pytest.raises(cv.Invalid) as exc:
|
||||
SPEC.validate_supports(_conf(num, hold=True, input=True))
|
||||
assert exc.value.msg == message
|
||||
assert exc.value.path == [CONF_HOLD_STATE]
|
||||
|
||||
|
||||
def test_usb_jtag_hold_and_strapping_warnings(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with caplog.at_level("WARNING"):
|
||||
SPEC.validate_supports(_conf(3, hold=True, output=True))
|
||||
assert len(caplog.messages) == 2
|
||||
assert caplog.messages[0] == (
|
||||
"GPIO3 cannot hold at low level during wakeup from deep sleep."
|
||||
)
|
||||
assert caplog.messages[1].startswith("GPIO3 is a strapping pin")
|
||||
|
||||
|
||||
def test_ignore_strapping_warning_on_normal_pin_rejected() -> None:
|
||||
conf = _conf(5, input=True)
|
||||
conf[CONF_IGNORE_STRAPPING_WARNING] = True
|
||||
with pytest.raises(cv.Invalid, match="GPIO5 is not a strapping pin"):
|
||||
SPEC.validate_supports(conf)
|
||||
|
||||
|
||||
_USB_JTAG = "is used by the USB-Serial-JTAG interface"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "num", "error", "warning"),
|
||||
[
|
||||
(VARIANT_ESP32, 4, None, None),
|
||||
(
|
||||
VARIANT_ESP32,
|
||||
6,
|
||||
"used by the flash interface (function: Flash Clock).",
|
||||
None,
|
||||
),
|
||||
(VARIANT_ESP32, 9, None, "Pin 9 (9-10) might already be used by the flash"),
|
||||
(VARIANT_ESP32, 24, "The pin GPIO24 is not usable on ESP32s.", None),
|
||||
(VARIANT_ESP32, 40, "Invalid pin number: 40 (must be 0-39)", None),
|
||||
(VARIANT_ESP32C2, 8, None, None),
|
||||
(VARIANT_ESP32C2, 21, "Invalid pin number: 21 (must be 0-20)", None),
|
||||
(
|
||||
VARIANT_ESP32C3,
|
||||
12,
|
||||
"used on ESP32-C3s and is already used by the SPI/PSRAM",
|
||||
None,
|
||||
),
|
||||
(VARIANT_ESP32C3, 18, None, _USB_JTAG),
|
||||
(
|
||||
VARIANT_ESP32C5,
|
||||
19,
|
||||
"ESP32-C5s and is already used by the SPI/PSRAM interface",
|
||||
None,
|
||||
),
|
||||
(VARIANT_ESP32C5, 13, None, _USB_JTAG),
|
||||
(VARIANT_ESP32C6, 27, "SPI/PSRAM interface (function: VDD_SPI)", None),
|
||||
(VARIANT_ESP32C6, 12, None, _USB_JTAG),
|
||||
(
|
||||
VARIANT_ESP32C61,
|
||||
21,
|
||||
"used on ESP32-C61s and is already used by the SPI/PSRAM",
|
||||
None,
|
||||
),
|
||||
(VARIANT_ESP32C61, 18, None, None),
|
||||
(
|
||||
VARIANT_ESP32H2,
|
||||
6,
|
||||
None,
|
||||
"GPIO6 is reserved for SPI Flash communication on some",
|
||||
),
|
||||
(VARIANT_ESP32H2, 26, None, _USB_JTAG),
|
||||
(VARIANT_ESP32H2, 28, "Invalid pin number: 28 (must be 0-27)", None),
|
||||
(VARIANT_ESP32H21, 17, None, _USB_JTAG),
|
||||
(VARIANT_ESP32H4, 13, None, _USB_JTAG),
|
||||
(VARIANT_ESP32H4, 40, "Invalid pin number: 40 (must be 0-39)", None),
|
||||
(VARIANT_ESP32P4, 24, None, _USB_JTAG),
|
||||
(VARIANT_ESP32P4, 55, "Invalid pin number: 55 (must be 0-54)", None),
|
||||
(
|
||||
VARIANT_ESP32S2,
|
||||
26,
|
||||
"ESP32-S2s and is already used by the SPI/PSRAM interface",
|
||||
None,
|
||||
),
|
||||
(VARIANT_ESP32S2, 22, "The pin GPIO22 is not usable on ESP32-S2s.", None),
|
||||
(VARIANT_ESP32S3, 33, None, None),
|
||||
(VARIANT_ESP32S3, 19, None, _USB_JTAG),
|
||||
(VARIANT_ESP32S3, 25, "The pin GPIO25 is not usable on ESP32-S3s.", None),
|
||||
(VARIANT_ESP32S3, 32, "SPI/PSRAM interface (function: SPID)", None),
|
||||
(
|
||||
VARIANT_ESP32S31,
|
||||
26,
|
||||
"GPIO26 is reserved for the SPI flash interface on ESP32-S31",
|
||||
None,
|
||||
),
|
||||
(VARIANT_ESP32S31, 29, "GPIO29 does not exist on ESP32-S31.", None),
|
||||
(VARIANT_ESP32S31, 33, None, _USB_JTAG),
|
||||
(VARIANT_ESP32S31, 62, "Invalid pin number: 62 (must be 0-61)", None),
|
||||
],
|
||||
)
|
||||
def test_variant_pin_validation(
|
||||
variant: str,
|
||||
num: int,
|
||||
error: str | None,
|
||||
warning: str | None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
spec = _esp32_pin_specs[variant]
|
||||
with caplog.at_level("WARNING"):
|
||||
if error is None:
|
||||
assert spec.validate_gpio_pin(num) == num
|
||||
else:
|
||||
with pytest.raises(cv.Invalid) as exc:
|
||||
spec.validate_gpio_pin(num)
|
||||
assert error in exc.value.msg
|
||||
if warning is None:
|
||||
assert not caplog.messages
|
||||
else:
|
||||
assert len(caplog.messages) == 1
|
||||
assert warning in caplog.messages[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "conf", "error"),
|
||||
[
|
||||
(
|
||||
VARIANT_ESP32,
|
||||
_conf(36, output=True),
|
||||
"GPIO36 (34-39) does not support output",
|
||||
),
|
||||
(
|
||||
VARIANT_ESP32,
|
||||
_conf(39, pullup=True),
|
||||
"GPIO39 (34-39) does not support pullups.",
|
||||
),
|
||||
(
|
||||
VARIANT_ESP32,
|
||||
_conf(34, hold=True),
|
||||
"GPIO34 (34-39) is input-only and cannot",
|
||||
),
|
||||
(VARIANT_ESP32, _conf(20, hold=True), "GPIO20 has no hold function."),
|
||||
(VARIANT_ESP32, _conf(40), "Invalid pin number: 40 (must be 0-39)"),
|
||||
(
|
||||
VARIANT_ESP32S2,
|
||||
_conf(46, pulldown=True),
|
||||
"GPIO46 does not support pulldowns.",
|
||||
),
|
||||
(
|
||||
VARIANT_ESP32S2,
|
||||
_conf(46, hold=True),
|
||||
"GPIO46 is input-only and cannot be held.",
|
||||
),
|
||||
(VARIANT_ESP32S31, _conf(41), "GPIO41 does not exist on ESP32-S31."),
|
||||
(VARIANT_ESP32C2, _conf(21), "Invalid pin number: 21 (must be 0-20)"),
|
||||
],
|
||||
)
|
||||
def test_variant_supports_errors(variant: str, conf: ConfigType, error: str) -> None:
|
||||
with pytest.raises(cv.Invalid) as exc:
|
||||
_esp32_pin_specs[variant].validate_supports(conf)
|
||||
assert error in exc.value.msg
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "conf", "warnings"),
|
||||
[
|
||||
(VARIANT_ESP32, _conf(0, output=True), ["GPIO0 is a strapping pin"]),
|
||||
(VARIANT_ESP32, _conf(35, input=True), []),
|
||||
(VARIANT_ESP32C3, _conf(18, hold=True), ["GPIO18 cannot hold at low level"]),
|
||||
(
|
||||
VARIANT_ESP32H4,
|
||||
_conf(13, hold=True),
|
||||
["GPIO13 cannot hold at low level", "GPIO13 is a strapping pin"],
|
||||
),
|
||||
(VARIANT_ESP32S3, _conf(46, input=True), ["GPIO46 is a strapping pin"]),
|
||||
],
|
||||
)
|
||||
def test_variant_supports_warnings(
|
||||
variant: str,
|
||||
conf: ConfigType,
|
||||
warnings: list[str],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with caplog.at_level("WARNING"):
|
||||
_esp32_pin_specs[variant].validate_supports(conf)
|
||||
assert len(caplog.messages) == len(warnings)
|
||||
for message, expected in zip(caplog.messages, warnings, strict=True):
|
||||
assert message.startswith(expected)
|
||||
@@ -72,23 +72,6 @@ button:
|
||||
id(ota_update).set_source_url(std::string("http://my.ha.net:8123/local/esphome/manifest.json"));
|
||||
ESP_LOGI("test", "Manifest is now %s", id(ota_update).get_source_url());
|
||||
- component.update: ota_update
|
||||
# The OTA component's public setters and getters as lambdas use them; every shape must compile.
|
||||
- platform: template
|
||||
name: Flash from lambda
|
||||
on_press:
|
||||
then:
|
||||
- lambda: |-
|
||||
std::string url = "http://my.ha.net:8123/local/esphome/";
|
||||
url += "firmware.bin";
|
||||
id(http_request_ota).set_md5_url("http://my.ha.net:8123/local/esphome/firmware.md5");
|
||||
id(http_request_ota).set_md5(std::string("0123456789abcdef0123456789abcdef"));
|
||||
id(http_request_ota).set_username("user");
|
||||
id(http_request_ota).set_password(std::string("pass"));
|
||||
id(http_request_ota).set_url(url);
|
||||
id(http_request_ota).set_url(url.c_str());
|
||||
ESP_LOGI("test", "MD5 %s / %s", id(http_request_ota).md5_expected().c_str(),
|
||||
id(http_request_ota).md5_computed().c_str());
|
||||
id(http_request_ota).flash();
|
||||
|
||||
update:
|
||||
- platform: http_request
|
||||
|
||||
Reference in new issue
Block a user