mirror of
https://github.com/esphome/esphome.git
synced 2024-12-27 07:51:43 +01:00
dbb195691b
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto winter <otto@otto-winter.com>
330 lines
9.2 KiB
Python
330 lines
9.2 KiB
Python
import logging
|
|
|
|
import esphome.config_validation as cv
|
|
from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER
|
|
from esphome.core import CORE
|
|
from esphome.util import SimpleRegistry
|
|
from esphome import boards
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
def _lookup_pin(value):
|
|
if CORE.is_esp8266:
|
|
board_pins_dict = boards.ESP8266_BOARD_PINS
|
|
base_pins = boards.ESP8266_BASE_PINS
|
|
elif CORE.is_esp32:
|
|
if CORE.board in boards.ESP32_C3_BOARD_PINS:
|
|
board_pins_dict = boards.ESP32_C3_BOARD_PINS
|
|
base_pins = boards.ESP32_C3_BASE_PINS
|
|
else:
|
|
board_pins_dict = boards.ESP32_BOARD_PINS
|
|
base_pins = boards.ESP32_BASE_PINS
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
board_pins = board_pins_dict.get(CORE.board, {})
|
|
|
|
# Resolved aliased board pins (shorthand when two boards have the same pin configuration)
|
|
while isinstance(board_pins, str):
|
|
board_pins = board_pins_dict[board_pins]
|
|
|
|
if value in board_pins:
|
|
return board_pins[value]
|
|
if value in base_pins:
|
|
return base_pins[value]
|
|
raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {CORE.board}.")
|
|
|
|
|
|
def _translate_pin(value):
|
|
if isinstance(value, dict) or value is None:
|
|
raise cv.Invalid(
|
|
"This variable only supports pin numbers, not full pin schemas "
|
|
"(with inverted and mode)."
|
|
)
|
|
if isinstance(value, int):
|
|
return value
|
|
try:
|
|
return int(value)
|
|
except ValueError:
|
|
pass
|
|
if value.startswith("GPIO"):
|
|
return cv.Coerce(int)(value[len("GPIO") :].strip())
|
|
return _lookup_pin(value)
|
|
|
|
|
|
_ESP_SDIO_PINS = {
|
|
6: "Flash Clock",
|
|
7: "Flash Data 0",
|
|
8: "Flash Data 1",
|
|
11: "Flash Command",
|
|
}
|
|
|
|
_ESP32C3_SDIO_PINS = {
|
|
12: "Flash IO3/HOLD#",
|
|
13: "Flash IO2/WP#",
|
|
14: "Flash CS#",
|
|
15: "Flash CLK",
|
|
16: "Flash IO0/DI",
|
|
17: "Flash IO1/DO",
|
|
}
|
|
|
|
|
|
def validate_gpio_pin(value):
|
|
value = _translate_pin(value)
|
|
if CORE.is_esp32_c3:
|
|
if value < 0 or value > 22:
|
|
raise cv.Invalid(f"ESP32-C3: Invalid pin number: {value}")
|
|
if value in _ESP32C3_SDIO_PINS:
|
|
raise cv.Invalid(
|
|
f"This pin cannot be used on ESP32-C3s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
|
|
)
|
|
return value
|
|
if CORE.is_esp32:
|
|
if value < 0 or value > 39:
|
|
raise cv.Invalid(f"ESP32: Invalid pin number: {value}")
|
|
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(
|
|
"ESP32: 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
|
|
if CORE.is_esp8266:
|
|
if value < 0 or value > 17:
|
|
raise cv.Invalid(f"ESP8266: Invalid pin number: {value}")
|
|
if value in _ESP_SDIO_PINS:
|
|
raise cv.Invalid(
|
|
f"This pin cannot be used on ESP8266s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
|
|
)
|
|
if 9 <= value <= 10:
|
|
_LOGGER.warning(
|
|
"ESP8266: Pin %s (9-10) might already be used by the "
|
|
"flash interface in QUAD IO flash mode.",
|
|
value,
|
|
)
|
|
return value
|
|
raise NotImplementedError
|
|
|
|
|
|
def input_pin(value):
|
|
value = validate_gpio_pin(value)
|
|
if CORE.is_esp8266 and value == 17:
|
|
raise cv.Invalid("GPIO17 (TOUT) is an analog-only pin on the ESP8266.")
|
|
return value
|
|
|
|
|
|
def input_pullup_pin(value):
|
|
value = input_pin(value)
|
|
if CORE.is_esp32:
|
|
return output_pin(value)
|
|
if CORE.is_esp8266:
|
|
if value == 0:
|
|
raise cv.Invalid(
|
|
"GPIO Pin 0 does not support pullup pin mode. "
|
|
"Please choose another pin."
|
|
)
|
|
return value
|
|
raise NotImplementedError
|
|
|
|
|
|
def output_pin(value):
|
|
value = validate_gpio_pin(value)
|
|
if CORE.is_esp32:
|
|
if 34 <= value <= 39:
|
|
raise cv.Invalid(
|
|
f"ESP32: GPIO{value} (34-39) can only be used as an input pin."
|
|
)
|
|
return value
|
|
if CORE.is_esp8266:
|
|
if value == 17:
|
|
raise cv.Invalid("GPIO17 (TOUT) is an analog-only pin on the ESP8266.")
|
|
return value
|
|
raise NotImplementedError
|
|
|
|
|
|
def analog_pin(value):
|
|
value = validate_gpio_pin(value)
|
|
if CORE.is_esp32:
|
|
if CORE.is_esp32_c3:
|
|
if 0 <= value <= 4: # ADC1
|
|
return value
|
|
raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.")
|
|
if 32 <= value <= 39: # ADC1
|
|
return value
|
|
raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
|
|
if CORE.is_esp8266:
|
|
if value == 17: # A0
|
|
return value
|
|
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
|
|
raise NotImplementedError
|
|
|
|
|
|
input_output_pin = cv.All(input_pin, output_pin)
|
|
|
|
PIN_MODES_ESP8266 = [
|
|
"INPUT",
|
|
"OUTPUT",
|
|
"INPUT_PULLUP",
|
|
"OUTPUT_OPEN_DRAIN",
|
|
"SPECIAL",
|
|
"FUNCTION_1",
|
|
"FUNCTION_2",
|
|
"FUNCTION_3",
|
|
"FUNCTION_4",
|
|
"FUNCTION_0",
|
|
"WAKEUP_PULLUP",
|
|
"WAKEUP_PULLDOWN",
|
|
"INPUT_PULLDOWN_16",
|
|
]
|
|
PIN_MODES_ESP32 = [
|
|
"INPUT",
|
|
"OUTPUT",
|
|
"INPUT_PULLUP",
|
|
"OUTPUT_OPEN_DRAIN",
|
|
"SPECIAL",
|
|
"FUNCTION_1",
|
|
"FUNCTION_2",
|
|
"FUNCTION_3",
|
|
"FUNCTION_4",
|
|
"PULLUP",
|
|
"PULLDOWN",
|
|
"INPUT_PULLDOWN",
|
|
"OPEN_DRAIN",
|
|
"FUNCTION_5",
|
|
"FUNCTION_6",
|
|
"ANALOG",
|
|
]
|
|
|
|
|
|
def pin_mode(value):
|
|
if CORE.is_esp32:
|
|
return cv.one_of(*PIN_MODES_ESP32, upper=True)(value)
|
|
if CORE.is_esp8266:
|
|
return cv.one_of(*PIN_MODES_ESP8266, upper=True)(value)
|
|
raise NotImplementedError
|
|
|
|
|
|
GPIO_FULL_OUTPUT_PIN_SCHEMA = cv.Schema(
|
|
{
|
|
cv.Required(CONF_NUMBER): output_pin,
|
|
cv.Optional(CONF_MODE, default="OUTPUT"): pin_mode,
|
|
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
|
}
|
|
)
|
|
|
|
GPIO_FULL_INPUT_PIN_SCHEMA = cv.Schema(
|
|
{
|
|
cv.Required(CONF_NUMBER): input_pin,
|
|
cv.Optional(CONF_MODE, default="INPUT"): pin_mode,
|
|
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
|
}
|
|
)
|
|
|
|
GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA = cv.Schema(
|
|
{
|
|
cv.Required(CONF_NUMBER): input_pin,
|
|
cv.Optional(CONF_MODE, default="INPUT_PULLUP"): pin_mode,
|
|
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
|
}
|
|
)
|
|
|
|
GPIO_FULL_ANALOG_PIN_SCHEMA = cv.Schema(
|
|
{
|
|
cv.Required(CONF_NUMBER): analog_pin,
|
|
cv.Optional(CONF_MODE, default="INPUT"): pin_mode,
|
|
}
|
|
)
|
|
|
|
|
|
def shorthand_output_pin(value):
|
|
value = output_pin(value)
|
|
return GPIO_FULL_OUTPUT_PIN_SCHEMA({CONF_NUMBER: value})
|
|
|
|
|
|
def shorthand_input_pin(value):
|
|
value = input_pin(value)
|
|
return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value})
|
|
|
|
|
|
def shorthand_input_pullup_pin(value):
|
|
value = input_pullup_pin(value)
|
|
return GPIO_FULL_INPUT_PIN_SCHEMA(
|
|
{
|
|
CONF_NUMBER: value,
|
|
CONF_MODE: "INPUT_PULLUP",
|
|
}
|
|
)
|
|
|
|
|
|
def shorthand_analog_pin(value):
|
|
value = analog_pin(value)
|
|
return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value})
|
|
|
|
|
|
def validate_has_interrupt(value):
|
|
if CORE.is_esp8266:
|
|
if value[CONF_NUMBER] >= 16:
|
|
raise cv.Invalid(
|
|
f"Pins GPIO16 and GPIO17 do not support interrupts and cannot be used here, got {value[CONF_NUMBER]}"
|
|
)
|
|
return value
|
|
|
|
|
|
PIN_SCHEMA_REGISTRY = SimpleRegistry()
|
|
|
|
|
|
def internal_gpio_output_pin_schema(value):
|
|
if isinstance(value, dict):
|
|
return GPIO_FULL_OUTPUT_PIN_SCHEMA(value)
|
|
return shorthand_output_pin(value)
|
|
|
|
|
|
def gpio_output_pin_schema(value):
|
|
if isinstance(value, dict):
|
|
for key, entry in PIN_SCHEMA_REGISTRY.items():
|
|
if key in value:
|
|
return entry[1][0](value)
|
|
return internal_gpio_output_pin_schema(value)
|
|
|
|
|
|
def internal_gpio_input_pin_schema(value):
|
|
if isinstance(value, dict):
|
|
return GPIO_FULL_INPUT_PIN_SCHEMA(value)
|
|
return shorthand_input_pin(value)
|
|
|
|
|
|
def internal_gpio_analog_pin_schema(value):
|
|
if isinstance(value, dict):
|
|
return GPIO_FULL_ANALOG_PIN_SCHEMA(value)
|
|
return shorthand_analog_pin(value)
|
|
|
|
|
|
def gpio_input_pin_schema(value):
|
|
if isinstance(value, dict):
|
|
for key, entry in PIN_SCHEMA_REGISTRY.items():
|
|
if key in value:
|
|
return entry[1][1](value)
|
|
return internal_gpio_input_pin_schema(value)
|
|
|
|
|
|
def internal_gpio_input_pullup_pin_schema(value):
|
|
if isinstance(value, dict):
|
|
return GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA(value)
|
|
return shorthand_input_pullup_pin(value)
|
|
|
|
|
|
def gpio_input_pullup_pin_schema(value):
|
|
if isinstance(value, dict):
|
|
for key, entry in PIN_SCHEMA_REGISTRY.items():
|
|
if key in value:
|
|
return entry[1][1](value)
|
|
return internal_gpio_input_pullup_pin_schema(value)
|