mirror of
https://github.com/esphome/esphome.git
synced 2025-11-17 23:35:47 +00:00
Support for LibreTiny platform (RTL8710, BK7231 & other modules) (#3509)
Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl> Co-authored-by: Sam Neirinck <git@samneirinck.com> Co-authored-by: David Buezas <dbuezas@users.noreply.github.com> Co-authored-by: Stroe Andrei Catalin <catalin2402@gmail.com> Co-authored-by: Sam Neirinck <github@samneirinck.be> Co-authored-by: Péter Sárközi <xmisterhu@gmail.com> Co-authored-by: Hajo Noerenberg <hn@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
22c0b0abaa
commit
a9630ac847
216
esphome/components/libretiny/gpio.py
Normal file
216
esphome/components/libretiny/gpio.py
Normal file
@@ -0,0 +1,216 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_ANALOG,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_INVERTED,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
from .const import (
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_LIBRETINY,
|
||||
LibreTinyComponent,
|
||||
libretiny_ns,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ArduinoInternalGPIOPin = libretiny_ns.class_(
|
||||
"ArduinoInternalGPIOPin", cg.InternalGPIOPin
|
||||
)
|
||||
|
||||
|
||||
def _is_name_deprecated(value):
|
||||
return value[0] in "DA" and value[1:].isnumeric()
|
||||
|
||||
|
||||
def _lookup_board_pins(board):
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
board_pins = component.board_pins.get(board, {})
|
||||
# Resolve aliased board pins (shorthand when two boards have the same pin configuration)
|
||||
while isinstance(board_pins, str):
|
||||
board_pins = board_pins[board_pins]
|
||||
return board_pins
|
||||
|
||||
|
||||
def _lookup_pin(value):
|
||||
board: str = CORE.data[KEY_LIBRETINY][KEY_BOARD]
|
||||
board_pins = _lookup_board_pins(board)
|
||||
|
||||
# check numeric pin values
|
||||
if isinstance(value, int):
|
||||
if value in board_pins.values() or not board_pins:
|
||||
# accept if pin number present in board pins
|
||||
# if board is not found, just accept all numeric values
|
||||
return value
|
||||
raise cv.Invalid(f"Pin number '{value}' is not usable for board {board}.")
|
||||
|
||||
# check textual pin names
|
||||
if isinstance(value, str):
|
||||
if not board_pins:
|
||||
# can't remap without known pin name
|
||||
raise cv.Invalid(
|
||||
f"Board {board} wasn't found. "
|
||||
f"Use 'GPIO#' (numeric value) instead of '{value}'."
|
||||
)
|
||||
|
||||
if value in board_pins:
|
||||
# pin name found, remap to numeric value
|
||||
if _is_name_deprecated(value):
|
||||
number = board_pins[value]
|
||||
# find all alternative pin names (except the deprecated)
|
||||
names = (
|
||||
k
|
||||
for k, v in board_pins.items()
|
||||
if v == number and not _is_name_deprecated(k)
|
||||
)
|
||||
# sort by shortest
|
||||
# favor P# or PA# names
|
||||
names = sorted(
|
||||
names,
|
||||
key=lambda x: len(x) - 99 if x[0] == "P" else len(x),
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"Using D# and A# pin numbering is deprecated. "
|
||||
"Please replace '%s' with one of: %s",
|
||||
value,
|
||||
", ".join(names),
|
||||
)
|
||||
return board_pins[value]
|
||||
|
||||
# pin name not found and not numeric
|
||||
raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.")
|
||||
|
||||
# unknown type of the value
|
||||
raise cv.Invalid(f"Unrecognized pin value '{value}'.")
|
||||
|
||||
|
||||
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
|
||||
# translate GPIO* and P* to a number, if possible
|
||||
# otherwise return unchanged value (i.e. pin PA05)
|
||||
try:
|
||||
if value.startswith("GPIO"):
|
||||
value = int(value[4:])
|
||||
elif value.startswith("P"):
|
||||
value = int(value[1:])
|
||||
except ValueError:
|
||||
pass
|
||||
return value
|
||||
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
value = _translate_pin(value)
|
||||
value = _lookup_pin(value)
|
||||
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
if component.pin_validation:
|
||||
value = component.pin_validation(value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def validate_gpio_usage(value):
|
||||
mode = value[CONF_MODE]
|
||||
is_analog = mode[CONF_ANALOG]
|
||||
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]
|
||||
|
||||
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_analog and not is_input:
|
||||
raise cv.Invalid("Analog pins must be an input", [CONF_MODE, CONF_ANALOG])
|
||||
if is_analog:
|
||||
# expect analog pin numbers to be available as either ADC# or A#
|
||||
number = value[CONF_NUMBER]
|
||||
board: str = CORE.data[KEY_LIBRETINY][KEY_BOARD]
|
||||
board_pins = _lookup_board_pins(board)
|
||||
analog_pins = [
|
||||
v
|
||||
for k, v in board_pins.items()
|
||||
if k[0] == "A" and k[1:].isnumeric() or k[0:3] == "ADC"
|
||||
]
|
||||
if number not in analog_pins:
|
||||
raise cv.Invalid(
|
||||
f"Pin '{number}' is not an analog pin", [CONF_MODE, CONF_ANALOG]
|
||||
)
|
||||
|
||||
# (input, output, open_drain, pullup, pulldown)
|
||||
supported_modes = {
|
||||
# INPUT
|
||||
(True, False, False, False, False),
|
||||
# OUTPUT
|
||||
(False, True, False, False, False),
|
||||
# INPUT_PULLUP
|
||||
(True, False, False, True, False),
|
||||
# INPUT_PULLDOWN
|
||||
(True, False, False, False, True),
|
||||
# OUTPUT_OPEN_DRAIN
|
||||
(False, True, True, False, False),
|
||||
}
|
||||
key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown)
|
||||
if key not in supported_modes:
|
||||
raise cv.Invalid("This pin mode is not supported", [CONF_MODE])
|
||||
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
if component.usage_validation:
|
||||
value = component.usage_validation(value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
BASE_PIN_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ArduinoInternalGPIOPin),
|
||||
cv.Required(CONF_NUMBER): validate_gpio_pin,
|
||||
cv.Optional(CONF_MODE, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ANALOG, default=False): cv.boolean,
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
},
|
||||
)
|
||||
|
||||
BASE_PIN_SCHEMA.add_extra(validate_gpio_usage)
|
||||
|
||||
|
||||
async def component_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
Reference in New Issue
Block a user