mirror of
https://github.com/esphome/esphome.git
synced 2025-09-13 08:42:18 +01:00
ESP-IDF support and generic target platforms (#2303)
* Socket refactor and SSL * esp-idf temp * Fixes * Echo component and noise * Add noise API transport support * Updates * ESP-IDF * Complete * Fixes * Fixes * Versions update * New i2c APIs * Complete i2c refactor * SPI migration * Revert ESP Preferences migration, too complex for now * OTA support * Remove echo again * Remove ssl again * GPIOFlags updates * Rename esphal and ICACHE_RAM_ATTR * Make ESP32 arduino compilable again * Fix GPIO flags * Complete pin registry refactor and fixes * Fixes to make test1 compile * Remove sdkconfig file * Ignore sdkconfig file * Fixes in reviewing * Make test2 compile * Make test4 compile * Make test5 compile * Run clang-format * Fix lint errors * Use esp-idf APIs instead of btStart * Another round of fixes * Start implementing ESP8266 * Make test3 compile * Guard esp8266 code * Lint * Reformat * Fixes * Fixes v2 * more fixes * ESP-IDF tidy target * Convert ARDUINO_ARCH_ESPxx * Update WiFiSignalSensor * Update time ifdefs * OTA needs millis from hal * RestartSwitch needs delay from hal * ESP-IDF Uart * Fix OTA blank password * Allow setting sdkconfig * Fix idf partitions and allow setting sdkconfig from yaml * Re-add read/write compat APIs and fix esp8266 uart * Fix esp8266 store log strings in flash * Fix ESP32 arduino preferences not initialized * Update ifdefs * Change how sdkconfig change is detected * Add checks to ci-custom and fix them * Run clang-format * Add esp-idf clang-tidy target and fix errors * Fixes from clang-tidy idf round 2 * Fixes from compiling tests with esp-idf * Run clang-format * Switch test5.yaml to esp-idf * Implement ESP8266 Preferences * Lint * Re-do PIO package version selection a bit * Fix arduinoespressif32 package version * Fix unit tests * Lint * Lint fixes * Fix readv/writev not defined * Fix graphing component * Re-add all old options from core/config.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
443
esphome/pins.py
443
esphome/pins.py
@@ -1,330 +1,143 @@
|
||||
import logging
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER
|
||||
from esphome.core import CORE
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
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
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
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 _set_mode(value, default_mode):
|
||||
import esphome.config_validation as cv
|
||||
|
||||
if CONF_MODE not in value:
|
||||
return {**value, CONF_MODE: default_mode}
|
||||
mode = value[CONF_MODE]
|
||||
if not isinstance(mode, str):
|
||||
return value
|
||||
# mode is a string, try parsing it like arduino pin modes
|
||||
PIN_MODES = {
|
||||
"INPUT": {
|
||||
CONF_INPUT: True,
|
||||
},
|
||||
"OUTPUT": {
|
||||
CONF_OUTPUT: True,
|
||||
},
|
||||
"INPUT_PULLUP": {
|
||||
CONF_INPUT: True,
|
||||
CONF_PULLUP: True,
|
||||
},
|
||||
"OUTPUT_OPEN_DRAIN": {
|
||||
CONF_OUTPUT: True,
|
||||
CONF_OPEN_DRAIN: True,
|
||||
},
|
||||
"INPUT_PULLDOWN_16": {
|
||||
CONF_INPUT: True,
|
||||
CONF_PULLDOWN: True,
|
||||
},
|
||||
"INPUT_PULLDOWN": {
|
||||
CONF_INPUT: True,
|
||||
CONF_PULLDOWN: True,
|
||||
},
|
||||
}
|
||||
if mode.upper() not in PIN_MODES:
|
||||
raise cv.Invalid(f"Unknown pin mode {mode}", [CONF_MODE])
|
||||
return {**value, CONF_MODE: PIN_MODES[mode.upper()]}
|
||||
|
||||
|
||||
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 _schema_creator(default_mode, internal: bool = False):
|
||||
def validator(value):
|
||||
if not isinstance(value, dict):
|
||||
return validator({CONF_NUMBER: value})
|
||||
value = _set_mode(value, default_mode)
|
||||
if not internal:
|
||||
for key, entry in PIN_SCHEMA_REGISTRY.items():
|
||||
if key != CORE.target_platform and key in value:
|
||||
return entry[1](value)
|
||||
return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
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_number_creator(mode):
|
||||
def validator(value):
|
||||
value_d = {CONF_NUMBER: value}
|
||||
value_d = _set_mode(value_d, mode)
|
||||
return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value_d)[CONF_NUMBER]
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
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_flags_expr(mode):
|
||||
"""Convert the given mode dict to a gpio Flags expression"""
|
||||
import esphome.codegen as cg
|
||||
|
||||
FLAGS_MAPPING = {
|
||||
CONF_INPUT: cg.gpio_Flags.FLAG_INPUT,
|
||||
CONF_OUTPUT: cg.gpio_Flags.FLAG_OUTPUT,
|
||||
CONF_OPEN_DRAIN: cg.gpio_Flags.FLAG_OPEN_DRAIN,
|
||||
CONF_PULLUP: cg.gpio_Flags.FLAG_PULLUP,
|
||||
CONF_PULLDOWN: cg.gpio_Flags.FLAG_PULLDOWN,
|
||||
}
|
||||
active_flags = [v for k, v in FLAGS_MAPPING.items() if mode.get(k)]
|
||||
if active_flags:
|
||||
return cg.gpio_Flags.FLAG_NONE
|
||||
|
||||
return reduce(operator.or_, active_flags)
|
||||
|
||||
|
||||
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)
|
||||
gpio_pin_schema = _schema_creator
|
||||
internal_gpio_pin_number = _internal_number_creator
|
||||
gpio_output_pin_schema = _schema_creator(
|
||||
{
|
||||
CONF_OUTPUT: True,
|
||||
}
|
||||
)
|
||||
gpio_input_pin_schema = _schema_creator(
|
||||
{
|
||||
CONF_INPUT: True,
|
||||
}
|
||||
)
|
||||
gpio_input_pullup_pin_schema = _schema_creator(
|
||||
{
|
||||
CONF_INPUT: True,
|
||||
CONF_PULLUP: True,
|
||||
}
|
||||
)
|
||||
internal_gpio_output_pin_schema = _schema_creator(
|
||||
{
|
||||
CONF_OUTPUT: True,
|
||||
},
|
||||
internal=True,
|
||||
)
|
||||
internal_gpio_output_pin_number = _internal_number_creator({CONF_OUTPUT: True})
|
||||
internal_gpio_input_pin_schema = _schema_creator(
|
||||
{
|
||||
CONF_INPUT: True,
|
||||
},
|
||||
internal=True,
|
||||
)
|
||||
internal_gpio_input_pin_number = _internal_number_creator({CONF_INPUT: True})
|
||||
internal_gpio_input_pullup_pin_schema = _schema_creator(
|
||||
{
|
||||
CONF_INPUT: True,
|
||||
CONF_PULLUP: True,
|
||||
},
|
||||
internal=True,
|
||||
)
|
||||
internal_gpio_input_pullup_pin_number = _internal_number_creator(
|
||||
{
|
||||
CONF_INPUT: True,
|
||||
CONF_PULLUP: True,
|
||||
}
|
||||
)
|
||||
|
Reference in New Issue
Block a user