esphome/esphome/pins.py
Łukasz Śliwiński bdbd813455
Use proper schema for the analog pin shorthand (#2103)
The wrong error message is displayed like:
> GPIO17 (TOUT) is an analog-only pin on the ESP8266.
in case of the analog pin validation because the method `shorthand_analog_pin` uses wrong `GPIO_FULL_INPUT_PIN_SCHEMA` instead of `GPIO_FULL_ANALOG_PIN_SCHEMA`.
2021-08-04 17:46:09 +02:00

335 lines
9.4 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(
"This pin cannot be used on ESP32-C3s and is already used by "
"the flash interface (function: {})".format(_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(
"This pin cannot be used on ESP32s and is already used by "
"the flash interface (function: {})".format(_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(
"This pin cannot be used on ESP8266s and is already used by "
"the flash interface (function: {})".format(_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(
"ESP32: GPIO{} (34-39) can only be used as an "
"input pin.".format(value)
)
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(
"Pins GPIO16 and GPIO17 do not support interrupts and cannot be used "
"here, got {}".format(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)