mirror of
https://github.com/esphome/esphome.git
synced 2025-03-21 10:08:15 +00:00
346 lines
11 KiB
Python
346 lines
11 KiB
Python
import json
|
|
import logging
|
|
from os.path import (
|
|
dirname,
|
|
isfile,
|
|
join,
|
|
)
|
|
|
|
import esphome.codegen as cg
|
|
import esphome.config_validation as cv
|
|
from esphome.const import (
|
|
CONF_BOARD,
|
|
CONF_COMPONENT_ID,
|
|
CONF_DEBUG,
|
|
CONF_FAMILY,
|
|
CONF_FRAMEWORK,
|
|
CONF_ID,
|
|
CONF_NAME,
|
|
CONF_OPTIONS,
|
|
CONF_PROJECT,
|
|
CONF_SOURCE,
|
|
CONF_VERSION,
|
|
KEY_CORE,
|
|
KEY_FRAMEWORK_VERSION,
|
|
KEY_TARGET_FRAMEWORK,
|
|
KEY_TARGET_PLATFORM,
|
|
__version__,
|
|
)
|
|
from esphome.core import CORE
|
|
|
|
from . import gpio # noqa
|
|
from .const import (
|
|
CONF_GPIO_RECOVER,
|
|
CONF_LOGLEVEL,
|
|
CONF_SDK_SILENT,
|
|
CONF_UART_PORT,
|
|
FAMILIES,
|
|
FAMILY_COMPONENT,
|
|
FAMILY_FRIENDLY,
|
|
KEY_BOARD,
|
|
KEY_COMPONENT,
|
|
KEY_COMPONENT_DATA,
|
|
KEY_FAMILY,
|
|
KEY_LIBRETINY,
|
|
LT_DEBUG_MODULES,
|
|
LT_LOGLEVELS,
|
|
LibreTinyComponent,
|
|
LTComponent,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
CODEOWNERS = ["@kuba2k2"]
|
|
AUTO_LOAD = []
|
|
|
|
|
|
def _detect_variant(value):
|
|
if KEY_LIBRETINY not in CORE.data:
|
|
raise cv.Invalid("Family component didn't populate core data properly!")
|
|
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
|
board = value[CONF_BOARD]
|
|
# read board-default family if not specified
|
|
if board not in component.boards:
|
|
if CONF_FAMILY not in value:
|
|
raise cv.Invalid(
|
|
"This board is unknown, if you are sure you want to compile with this board selection, "
|
|
f"override with option '{CONF_FAMILY}'",
|
|
path=[CONF_BOARD],
|
|
)
|
|
_LOGGER.warning(
|
|
"This board is unknown. Make sure the chosen chip component is correct.",
|
|
)
|
|
else:
|
|
family = component.boards[board][KEY_FAMILY]
|
|
if CONF_FAMILY in value and family != value[CONF_FAMILY]:
|
|
raise cv.Invalid(
|
|
f"Option '{CONF_FAMILY}' does not match selected board.",
|
|
path=[CONF_FAMILY],
|
|
)
|
|
value = value.copy()
|
|
value[CONF_FAMILY] = family
|
|
# read component name matching this family
|
|
value[CONF_COMPONENT_ID] = FAMILY_COMPONENT[value[CONF_FAMILY]]
|
|
# make sure the chosen component matches the family
|
|
if value[CONF_COMPONENT_ID] != component.name:
|
|
raise cv.Invalid(
|
|
f"The chosen family doesn't belong to '{component.name}' component. The correct component is '{value[CONF_COMPONENT_ID]}'",
|
|
path=[CONF_FAMILY],
|
|
)
|
|
return value
|
|
|
|
|
|
def _update_core_data(config):
|
|
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = config[CONF_COMPONENT_ID]
|
|
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
|
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
|
config[CONF_FRAMEWORK][CONF_VERSION]
|
|
)
|
|
CORE.data[KEY_LIBRETINY][KEY_BOARD] = config[CONF_BOARD]
|
|
CORE.data[KEY_LIBRETINY][KEY_COMPONENT] = config[CONF_COMPONENT_ID]
|
|
CORE.data[KEY_LIBRETINY][KEY_FAMILY] = config[CONF_FAMILY]
|
|
return config
|
|
|
|
|
|
def get_libretiny_component(core_obj=None):
|
|
return (core_obj or CORE).data[KEY_LIBRETINY][KEY_COMPONENT]
|
|
|
|
|
|
def get_libretiny_family(core_obj=None):
|
|
return (core_obj or CORE).data[KEY_LIBRETINY][KEY_FAMILY]
|
|
|
|
|
|
def only_on_family(*, supported=None, unsupported=None):
|
|
"""Config validator for features only available on some LibreTiny families."""
|
|
if supported is not None and not isinstance(supported, list):
|
|
supported = [supported]
|
|
if unsupported is not None and not isinstance(unsupported, list):
|
|
unsupported = [unsupported]
|
|
|
|
def validator_(obj):
|
|
family = get_libretiny_family()
|
|
if supported is not None and family not in supported:
|
|
raise cv.Invalid(
|
|
f"This feature is only available on {', '.join(supported)}"
|
|
)
|
|
if unsupported is not None and family in unsupported:
|
|
raise cv.Invalid(
|
|
f"This feature is not available on {', '.join(unsupported)}"
|
|
)
|
|
return obj
|
|
|
|
return validator_
|
|
|
|
|
|
def get_download_types(storage_json=None):
|
|
types = [
|
|
{
|
|
"title": "UF2 package (recommended)",
|
|
"description": "For flashing via web_server OTA or with ltchiptool (UART)",
|
|
"file": "firmware.uf2",
|
|
"download": f"{storage_json.name}.uf2",
|
|
},
|
|
]
|
|
|
|
build_dir = dirname(storage_json.firmware_bin_path)
|
|
outputs = join(build_dir, "firmware.json")
|
|
if not isfile(outputs):
|
|
return types
|
|
with open(outputs, encoding="utf-8") as f:
|
|
outputs = json.load(f)
|
|
for output in outputs:
|
|
if not output["public"]:
|
|
continue
|
|
suffix = output["filename"].partition(".")[2]
|
|
suffix = f"-{suffix}" if "." in suffix else f".{suffix}"
|
|
types.append(
|
|
{
|
|
"title": output["title"],
|
|
"description": output["description"],
|
|
"file": output["filename"],
|
|
"download": storage_json.name + suffix,
|
|
}
|
|
)
|
|
return types
|
|
|
|
|
|
def _notify_old_style(config):
|
|
if config:
|
|
raise cv.Invalid(
|
|
"The LibreTiny component is now split between supported chip families.\n"
|
|
"Migrate your config file to include a chip-based configuration, "
|
|
"instead of the 'libretiny:' block.\n"
|
|
"For example 'bk72xx:' or 'rtl87xx:'."
|
|
)
|
|
return config
|
|
|
|
|
|
# NOTE: Keep this in mind when updating the recommended version:
|
|
# * For all constants below, update platformio.ini (in this repo)
|
|
ARDUINO_VERSIONS = {
|
|
"dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"),
|
|
"latest": (cv.Version(0, 0, 0), None),
|
|
"recommended": (cv.Version(1, 5, 1), None),
|
|
}
|
|
|
|
|
|
def _check_framework_version(value):
|
|
value = value.copy()
|
|
|
|
if value[CONF_VERSION] in ARDUINO_VERSIONS:
|
|
if CONF_SOURCE in value:
|
|
raise cv.Invalid(
|
|
"Framework version needs to be explicitly specified when custom source is used."
|
|
)
|
|
|
|
version, source = ARDUINO_VERSIONS[value[CONF_VERSION]]
|
|
else:
|
|
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
|
source = value.get(CONF_SOURCE, None)
|
|
|
|
value[CONF_VERSION] = str(version)
|
|
value[CONF_SOURCE] = source
|
|
|
|
return value
|
|
|
|
|
|
def _check_debug_order(value):
|
|
debug = value[CONF_DEBUG]
|
|
if "NONE" in debug and "NONE" in debug[1:]:
|
|
raise cv.Invalid(
|
|
"'none' has to be specified before other modules, and only once",
|
|
path=[CONF_DEBUG],
|
|
)
|
|
return value
|
|
|
|
|
|
FRAMEWORK_SCHEMA = cv.All(
|
|
cv.Schema(
|
|
{
|
|
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
|
cv.Optional(CONF_SOURCE): cv.string_strict,
|
|
cv.Optional(CONF_LOGLEVEL, default="warn"): (
|
|
cv.one_of(*LT_LOGLEVELS, upper=True)
|
|
),
|
|
cv.Optional(CONF_DEBUG, default=[]): cv.ensure_list(
|
|
cv.one_of("NONE", *LT_DEBUG_MODULES, upper=True)
|
|
),
|
|
cv.Optional(CONF_SDK_SILENT, default="all"): (
|
|
cv.one_of("all", "auto", "none", lower=True)
|
|
),
|
|
cv.Optional(CONF_UART_PORT): cv.one_of(0, 1, 2, int=True),
|
|
cv.Optional(CONF_GPIO_RECOVER, default=True): cv.boolean,
|
|
cv.Optional(CONF_OPTIONS, default={}): {
|
|
cv.string_strict: cv.string,
|
|
},
|
|
}
|
|
),
|
|
_check_framework_version,
|
|
_check_debug_order,
|
|
)
|
|
|
|
CONFIG_SCHEMA = cv.All(_notify_old_style)
|
|
|
|
BASE_SCHEMA = cv.Schema(
|
|
{
|
|
cv.GenerateID(): cv.declare_id(LTComponent),
|
|
cv.Required(CONF_BOARD): cv.string_strict,
|
|
cv.Optional(CONF_FAMILY): cv.one_of(*FAMILIES, upper=True),
|
|
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
|
|
},
|
|
)
|
|
|
|
BASE_SCHEMA.add_extra(_detect_variant)
|
|
BASE_SCHEMA.add_extra(_update_core_data)
|
|
|
|
|
|
# pylint: disable=use-dict-literal
|
|
async def component_to_code(config):
|
|
var = cg.new_Pvariable(config[CONF_ID])
|
|
|
|
# setup board config
|
|
cg.add_platformio_option("board", config[CONF_BOARD])
|
|
cg.add_build_flag("-DUSE_LIBRETINY")
|
|
cg.add_build_flag(f"-DUSE_{config[CONF_COMPONENT_ID].upper()}")
|
|
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
|
|
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
|
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])
|
|
|
|
# force using arduino framework
|
|
cg.add_platformio_option("framework", "arduino")
|
|
cg.add_build_flag("-DUSE_ARDUINO")
|
|
|
|
# disable library compatibility checks
|
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
|
# include <Arduino.h> in every file
|
|
cg.add_platformio_option("build_src_flags", "-include Arduino.h")
|
|
# dummy version code
|
|
cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)"))
|
|
# decrease web server stack size (16k words -> 4k words)
|
|
cg.add_build_flag("-DCONFIG_ASYNC_TCP_STACK_SIZE=4096")
|
|
|
|
# build framework version
|
|
# if platform version is a valid version constraint, prefix the default package
|
|
framework = config[CONF_FRAMEWORK]
|
|
cv.platformio_version_constraint(framework[CONF_VERSION])
|
|
if str(framework[CONF_VERSION]) != "0.0.0":
|
|
cg.add_platformio_option("platform", f"libretiny @ {framework[CONF_VERSION]}")
|
|
elif framework[CONF_SOURCE]:
|
|
cg.add_platformio_option("platform", framework[CONF_SOURCE])
|
|
else:
|
|
cg.add_platformio_option("platform", "libretiny")
|
|
|
|
# apply LibreTiny options from framework: block
|
|
# setup LT logger to work nicely with ESPHome logger
|
|
lt_options = dict(
|
|
LT_LOGLEVEL="LT_LEVEL_" + framework[CONF_LOGLEVEL],
|
|
LT_LOGGER_CALLER=0,
|
|
LT_LOGGER_TASK=0,
|
|
LT_LOGGER_COLOR=1,
|
|
LT_USE_TIME=1,
|
|
)
|
|
# enable/disable per-module debugging
|
|
for module in framework[CONF_DEBUG]:
|
|
if module == "NONE":
|
|
# disable all modules
|
|
for module in LT_DEBUG_MODULES:
|
|
lt_options[f"LT_DEBUG_{module}"] = 0
|
|
else:
|
|
# enable one module
|
|
lt_options[f"LT_DEBUG_{module}"] = 1
|
|
# set SDK silencing mode
|
|
if framework[CONF_SDK_SILENT] == "all":
|
|
lt_options["LT_UART_SILENT_ENABLED"] = 1
|
|
lt_options["LT_UART_SILENT_ALL"] = 1
|
|
elif framework[CONF_SDK_SILENT] == "auto":
|
|
lt_options["LT_UART_SILENT_ENABLED"] = 1
|
|
lt_options["LT_UART_SILENT_ALL"] = 0
|
|
else:
|
|
lt_options["LT_UART_SILENT_ENABLED"] = 0
|
|
lt_options["LT_UART_SILENT_ALL"] = 0
|
|
# set default UART port
|
|
if (uart_port := framework.get(CONF_UART_PORT, None)) is not None:
|
|
lt_options["LT_UART_DEFAULT_PORT"] = uart_port
|
|
# add custom options
|
|
lt_options.update(framework[CONF_OPTIONS])
|
|
|
|
# apply ESPHome options from framework: block
|
|
cg.add_define("LT_GPIO_RECOVER", int(framework[CONF_GPIO_RECOVER]))
|
|
|
|
# build PlatformIO compiler flags
|
|
for name, value in sorted(lt_options.items()):
|
|
cg.add_build_flag(f"-D{name}={value}")
|
|
|
|
# custom output firmware name and version
|
|
if CONF_PROJECT in config:
|
|
cg.add_platformio_option(
|
|
"custom_fw_name", "esphome." + config[CONF_PROJECT][CONF_NAME]
|
|
)
|
|
cg.add_platformio_option(
|
|
"custom_fw_version", config[CONF_PROJECT][CONF_VERSION]
|
|
)
|
|
else:
|
|
cg.add_platformio_option("custom_fw_name", "esphome")
|
|
cg.add_platformio_option("custom_fw_version", __version__)
|
|
|
|
await cg.register_component(var, config)
|