From add484a2ea06ba2bf2091306d3a53bd8967214bc Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 8 Nov 2021 19:29:28 +0100 Subject: [PATCH] Fix gpio validation for esp32 variants (#2609) Co-authored-by: Otto Winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32/gpio.py | 96 +++++++++++++---------- esphome/components/esp32/gpio_esp32.py | 77 ++++++++++++++++++ esphome/components/esp32/gpio_esp32_c3.py | 53 +++++++++++++ esphome/components/esp32/gpio_esp32_h2.py | 11 +++ esphome/components/esp32/gpio_esp32_s2.py | 80 +++++++++++++++++++ esphome/components/esp32/gpio_esp32_s3.py | 74 +++++++++++++++++ 6 files changed, 348 insertions(+), 43 deletions(-) create mode 100644 esphome/components/esp32/gpio_esp32.py create mode 100644 esphome/components/esp32/gpio_esp32_c3.py create mode 100644 esphome/components/esp32/gpio_esp32_h2.py create mode 100644 esphome/components/esp32/gpio_esp32_s2.py create mode 100644 esphome/components/esp32/gpio_esp32_s3.py diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 93ab17db22..5819943f37 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -1,4 +1,5 @@ -import logging +from dataclasses import dataclass +from typing import Any from esphome.const import ( CONF_ID, @@ -17,10 +18,24 @@ import esphome.config_validation as cv import esphome.codegen as cg from . import boards -from .const import KEY_BOARD, KEY_ESP32, esp32_ns +from .const import ( + KEY_BOARD, + KEY_ESP32, + KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32H2, + esp32_ns, +) -_LOGGER = logging.getLogger(__name__) +from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports +from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports +from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports +from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports +from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin) @@ -59,65 +74,61 @@ def _translate_pin(value): return _lookup_pin(value) -_ESP_SDIO_PINS = { - 6: "Flash Clock", - 7: "Flash Data 0", - 8: "Flash Data 1", - 11: "Flash Command", +@dataclass +class ESP32ValidationFunctions: + pin_validation: Any + usage_validation: Any + + +_esp32_validations = { + VARIANT_ESP32: ESP32ValidationFunctions( + pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports + ), + VARIANT_ESP32S2: ESP32ValidationFunctions( + pin_validation=esp32_s2_validate_gpio_pin, + usage_validation=esp32_s2_validate_supports, + ), + VARIANT_ESP32C3: ESP32ValidationFunctions( + pin_validation=esp32_c3_validate_gpio_pin, + usage_validation=esp32_c3_validate_supports, + ), + VARIANT_ESP32S3: ESP32ValidationFunctions( + pin_validation=esp32_s3_validate_gpio_pin, + usage_validation=esp32_s3_validate_supports, + ), + VARIANT_ESP32H2: ESP32ValidationFunctions( + pin_validation=esp32_h2_validate_gpio_pin, + usage_validation=esp32_h2_validate_supports, + ), } def validate_gpio_pin(value): value = _translate_pin(value) - 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 (function: {_ESP_SDIO_PINS[value]})" - ) - 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 (20, 24, 28, 29, 30, 31): - # 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 + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") + + return _esp32_validations[variant].pin_validation(value) def validate_supports(value): - num = value[CONF_NUMBER] mode = value[CONF_MODE] is_input = mode[CONF_INPUT] is_output = mode[CONF_OUTPUT] is_open_drain = mode[CONF_OPEN_DRAIN] is_pullup = mode[CONF_PULLUP] is_pulldown = mode[CONF_PULLDOWN] + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") - 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_open_drain and not is_output: raise cv.Invalid( "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] ) - 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] - ) + value = _esp32_validations[variant].usage_validation(value) if CORE.using_arduino: # (input, output, open_drain, pullup, pulldown) supported_modes = { @@ -138,7 +149,6 @@ def validate_supports(value): "This pin mode is not supported on ESP32 for arduino frameworks", [CONF_MODE], ) - return value diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py new file mode 100644 index 0000000000..425d77b343 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32.py @@ -0,0 +1,77 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +import esphome.config_validation as cv + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + +_ESP32_STRAPPING_PINS = {0, 2, 4, 15} +_LOGGER = logging.getLogger(__name__) + + +def esp32_validate_gpio_pin(value): + 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 (function: {_ESP_SDIO_PINS[value]})" + ) + 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 _ESP32_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + if value in (20, 24, 28, 29, 30, 31): + # 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): + 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 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] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py new file mode 100644 index 0000000000..fc1cef29e5 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_c3.py @@ -0,0 +1,53 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) +import esphome.config_validation as cv + +_ESP32C3_SPI_PSRAM_PINS = { + 12: "SPIHD", + 13: "SPIWP", + 14: "SPICS0", + 15: "SPICLK", + 16: "SPID", + 17: "SPIQ", +} + +_ESP32C3_STRAPPING_PINS = {2, 8, 9} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_c3_validate_gpio_pin(value): + 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]})" + ) + if value in _ESP32C3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + return value + + +def esp32_c3_validate_supports(value): + 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: {value} (must be 0-21)") + + if is_input: + # All ESP32 pins support input mode + pass + return value diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py new file mode 100644 index 0000000000..5196ef0c09 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_h2.py @@ -0,0 +1,11 @@ +import esphome.config_validation as cv + + +def esp32_h2_validate_gpio_pin(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") + + +def esp32_h2_validate_supports(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") diff --git a/esphome/components/esp32/gpio_esp32_s2.py b/esphome/components/esp32/gpio_esp32_s2.py new file mode 100644 index 0000000000..db244b6259 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s2.py @@ -0,0 +1,80 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) + +import esphome.config_validation as cv + +_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__) + + +def esp32_s2_validate_gpio_pin(value): + 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 _ESP32S2_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + 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-S2s.") + + return value + + +def esp32_s2_validate_supports(value): + 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] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py new file mode 100644 index 0000000000..f729a757c2 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s3.py @@ -0,0 +1,74 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) + +import esphome.config_validation as cv + +_ESP_32S3_SPI_PSRAM_PINS = { + 26: "SPICS1", + 27: "SPIHD", + 28: "SPIWP", + 29: "SPICS0", + 30: "SPICLK", + 31: "SPIQ", + 32: "SPID", +} + +_ESP_32_ESP32_S3R8_PSRAM_PINS = { + 33: "SPIIO4", + 34: "SPIIO5", + 35: "SPIIO6", + 36: "SPIIO7", + 37: "SPIDQS", +} + +_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_s3_validate_gpio_pin(value): + if value < 0 or value > 48: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)") + + if value in _ESP_32S3_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: {_ESP_32S3_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP_32_ESP32_S3R8_PSRAM_PINS: + _LOGGER.warning( + "GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models", + value, + ) + + if value in _ESP_32S3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + 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.") + + return value + + +def esp32_s3_validate_supports(value): + 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-46)") + if is_input: + # All ESP32 pins support input mode + pass + return value