mirror of
https://github.com/esphome/esphome.git
synced 2026-09-03 19:46:02 +00:00
Merge remote-tracking branch 'origin/dev' into jesserockz-2026-584
# Conflicts: # esphome/components/api/__init__.py # platformio.ini
This commit is contained in:
@@ -32,7 +32,7 @@ runs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
|
||||
# downstream jobs rely on is preserved.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Set up uv
|
||||
# ``--system`` (below) installs into the setup-python interpreter;
|
||||
# no venv is created or restored by this workflow.
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull-request-only workflow: a save could never be shared and
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
# detects the activated venv via ``VIRTUAL_ENV`` so downstream jobs
|
||||
# that ``. venv/bin/activate`` see an identical layout.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
@@ -367,7 +367,7 @@ jobs:
|
||||
- name: Set up uv
|
||||
# Only needed on cache miss to populate the venv.
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
@@ -1095,7 +1095,7 @@ jobs:
|
||||
# install step (order-of-magnitude faster on cold boots,
|
||||
# with its own wheel cache). actions/setup-python still
|
||||
# provides the interpreter.
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pull request saves land in per-PR scopes nothing else can
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -84,6 +84,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -14,4 +14,4 @@ jobs:
|
||||
permissions:
|
||||
issues: write # issues.lock on closed issues
|
||||
pull-requests: write # issues.lock on closed pull requests
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@9f6577fd37b5cf773ab1b9be929714a0dcd15661 # 2026.7.0
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@0fdd5e311b7e744069166696072a1a9cbc5fbeb6 # 2026.8.1
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
# No GITHUB_TOKEN permissions: the reusable workflow mints an ESPHome
|
||||
# GitHub App token so the labels, comments and closures come from
|
||||
# esphome[bot] instead of github-actions[bot].
|
||||
uses: esphome/workflows/.github/workflows/stale.yml@61fd37a044cad4e9aa4303027b2a61b6a34da855 # main
|
||||
uses: esphome/workflows/.github/workflows/stale.yml@a1c1485ab46ef41a84a6a9d8abd7fa4b7628fd70 # main
|
||||
secrets:
|
||||
ESPHOME_GITHUB_APP_PRIVATE_KEY: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
with:
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
# setup-python interpreter so subsequent ``prek`` /
|
||||
# ``script/run-in-env.py`` steps find the deps without a
|
||||
# ``uv run`` prefix.
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
enable-cache: true
|
||||
# Pin uv version so the action does not have to fetch the
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.16.0
|
||||
rev: v0.16.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.11.0
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.11.1
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -45,8 +45,8 @@ CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
# Keep in sync with platformio.ini and esphome/idf_component.yml.
|
||||
# LIBSODIUM_VERSION must match the version noise-c pins in its idf_component.yml.
|
||||
NOISE_C_VERSION = "0.1.18"
|
||||
LIBSODIUM_VERSION = "1.10021.2"
|
||||
NOISE_C_VERSION = "0.1.19"
|
||||
LIBSODIUM_VERSION = "1.10021.3"
|
||||
|
||||
|
||||
def AUTO_LOAD(config: ConfigType) -> list[str]:
|
||||
|
||||
@@ -68,12 +68,11 @@ def _unsupported_family_message(family: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
# Warn only: a hard error here would break the validate-only CI fixtures,
|
||||
# which run on a BLE 4.2 board. The hard error is raised at codegen.
|
||||
if msg := _unsupported_family_message(libretiny.get_libretiny_family()):
|
||||
_LOGGER.warning("%s (this configuration cannot compile)", msg)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -61,7 +61,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
wifi_conf = full_config.get("wifi")
|
||||
|
||||
@@ -88,8 +88,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
socket.consume_sockets(3, "captive_portal")(config)
|
||||
socket.consume_sockets(1, "captive_portal", socket.SocketType.UDP)(config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ async def to_code(config):
|
||||
cg.add_library("esphome/dsmr_parser", "1.9.0")
|
||||
|
||||
|
||||
def final_validate(config: ConfigType) -> ConfigType:
|
||||
def final_validate(config: ConfigType) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
|
||||
for uart_conf in full_config["uart"]:
|
||||
@@ -102,7 +102,5 @@ def final_validate(config: ConfigType) -> ConfigType:
|
||||
)
|
||||
break
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
@@ -59,7 +59,7 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def final_validate(config: ConfigType) -> ConfigType:
|
||||
def final_validate(config: ConfigType) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
|
||||
# Count sensors registered to this hub (IDs are resolved at final_validate stage)
|
||||
@@ -95,7 +95,7 @@ def final_validate(config: ConfigType) -> ConfigType:
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)
|
||||
return schema(config)
|
||||
schema(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
@@ -153,7 +153,7 @@ def customise_schema(config):
|
||||
CONFIG_SCHEMA = customise_schema
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
spi.final_validate_device_schema(
|
||||
"epaper_spi", require_miso=False, require_mosi=True
|
||||
)(config)
|
||||
@@ -170,7 +170,6 @@ def _final_validate(config):
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
elif CONF_UPDATE_INTERVAL not in config:
|
||||
config[CONF_UPDATE_INTERVAL] = update_interval("1min")
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -1368,7 +1368,7 @@ def _validate_signed_ota_keys(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
def final_validate(config) -> None:
|
||||
# Imported locally to avoid circular import issues
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
|
||||
@@ -1629,8 +1629,6 @@ def final_validate(config):
|
||||
if errs:
|
||||
raise cv.MultipleInvalid(errs)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONF_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||
CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server"
|
||||
|
||||
@@ -443,7 +443,7 @@ def validate_connection_slots(max_connections: int) -> None:
|
||||
)
|
||||
|
||||
|
||||
def final_validation(config):
|
||||
def final_validation(config) -> None:
|
||||
validate_variant(config)
|
||||
if (name := config.get(CONF_NAME)) is not None:
|
||||
full_config = fv.full_config.get()
|
||||
@@ -514,8 +514,6 @@ def final_validation(config):
|
||||
# For newer chips (C3/S3/etc), different configs are used automatically
|
||||
add_idf_sdkconfig_option("CONFIG_BTDM_CTRL_BLE_MAX_CONN", max_connections)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validation
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ def create_device_information_service(config):
|
||||
return config
|
||||
|
||||
|
||||
def final_validate_config(config):
|
||||
def final_validate_config(config) -> None:
|
||||
# Validate max_clients does not exceed esp32_ble max_connections
|
||||
max_clients = config[CONF_MAX_CLIENTS]
|
||||
if max_clients > 1:
|
||||
@@ -355,7 +355,6 @@ def final_validate_config(config):
|
||||
raise cv.Invalid(
|
||||
f"Characteristic {char_config[CONF_UUID]} has both a set_value action and a templated value"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_value_type(value_config):
|
||||
|
||||
@@ -126,7 +126,7 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
# The esp_hosted releases compatible with older ESP-IDF versions crash at
|
||||
# boot with a heap double free in the SDIO RX path (fixed in esp_hosted
|
||||
# 2.11.0, which requires ESP-IDF 5.3), so reject them at validation time.
|
||||
@@ -136,7 +136,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
"Remove the framework version from your configuration to use the "
|
||||
"recommended version, or pin a version at or above 5.3."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -767,7 +767,7 @@ def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
raise cv.Invalid(error_msg, path=pin_path)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Final validation for Ethernet component."""
|
||||
# Allow ethernet + wifi coexistence only when both are declared in network: priority:.
|
||||
if "wifi" in fv.full_config.get():
|
||||
@@ -787,7 +787,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
|
||||
_final_validate_spi(config)
|
||||
_final_validate_rmii_pins(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -60,14 +60,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
if CORE.is_esp8266 and CONF_RESETS_REQUIRED in config:
|
||||
fconfig = full_config.get()
|
||||
if not fconfig.get_config_for_path([KEY_ESP8266, CONF_RESTORE_FROM_FLASH]):
|
||||
raise cv.Invalid(
|
||||
"'resets_required' needs 'restore_from_flash' to be enabled in the 'esp8266' configuration"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -225,7 +225,7 @@ def image_schema(class_: MockObjClass = Image_) -> cv.Schema:
|
||||
)
|
||||
|
||||
|
||||
def validate_image_final(config: ConfigType) -> ConfigType:
|
||||
def validate_image_final(config: ConfigType) -> None:
|
||||
"""Per-entry final validation, shared by file-backed image platforms.
|
||||
|
||||
For LVGL 9 the default byte order for RGB565 images is little-endian, so
|
||||
@@ -240,7 +240,6 @@ def validate_image_final(config: ConfigType) -> ConfigType:
|
||||
)
|
||||
else:
|
||||
config[CONF_BYTE_ORDER] = "LITTLE_ENDIAN"
|
||||
return config
|
||||
|
||||
|
||||
async def new_image(config: ConfigType) -> MockObj:
|
||||
|
||||
@@ -68,10 +68,10 @@ def _pin_shared_only_with_deep_sleep(pin_num: int) -> bool:
|
||||
return any(path and path[0] == "deep_sleep" for path, _, _ in pin_users)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
use_interrupt = config[CONF_USE_INTERRUPT]
|
||||
if not use_interrupt:
|
||||
return config
|
||||
return
|
||||
|
||||
# Expander pins (e.g. PCF8574, MCP23017) don't support direct interrupt
|
||||
# attachment — only internal/native GPIO pins do.
|
||||
@@ -82,7 +82,7 @@ def _final_validate(config):
|
||||
config.get(CONF_NAME, config[CONF_ID]),
|
||||
)
|
||||
config[CONF_USE_INTERRUPT] = False
|
||||
return config
|
||||
return
|
||||
|
||||
pin_num = config[CONF_PIN][CONF_NUMBER]
|
||||
|
||||
@@ -96,7 +96,7 @@ def _final_validate(config):
|
||||
config.get(CONF_NAME, config[CONF_ID]),
|
||||
)
|
||||
config[CONF_USE_INTERRUPT] = False
|
||||
return config
|
||||
return
|
||||
|
||||
# When a pin is shared, interrupts can interfere with other components
|
||||
# (e.g., duty_cycle sensor) that need to monitor the pin's state changes.
|
||||
@@ -120,8 +120,6 @@ def _final_validate(config):
|
||||
pin_num,
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@@ -163,8 +163,8 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("growatt_solar", role="client")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("growatt_solar", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -424,7 +424,7 @@ async def power_action_to_code(config, action_id, template_arg, args):
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
if CONF_LOGGER in full_config:
|
||||
_level = "NONE"
|
||||
@@ -448,7 +448,6 @@ def _final_validate(config):
|
||||
raise cv.Invalid(
|
||||
f"No WiFi configured, if you want to use haier climate without WiFi add {CONF_WIFI_SIGNAL}: false to climate configuration"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -60,7 +60,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
for switch_type in [CONF_BEEPER, CONF_QUIET_MODE]:
|
||||
# Check switches that are only supported for HonClimate
|
||||
@@ -72,7 +72,6 @@ def _final_validate(config):
|
||||
raise cv.Invalid(
|
||||
f"{switch_type} switch is only supported for hon climate"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -217,8 +217,8 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("havells_solar", role="client")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("havells_solar", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -315,7 +315,7 @@ def _validate_config(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Validate requirements when using HUB75 display."""
|
||||
# Local imports to avoid circular dependencies
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
@@ -381,8 +381,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
if errs:
|
||||
raise cv.MultipleInvalid(errs)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.Schema(_final_validate)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def validate_logger(config):
|
||||
def validate_logger(config) -> None:
|
||||
logger_conf = fv.full_config.get()[CONF_LOGGER]
|
||||
if logger_conf[CONF_BAUD_RATE] == 0:
|
||||
raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
|
||||
@@ -33,7 +33,6 @@ def validate_logger(config):
|
||||
raise cv.Invalid(
|
||||
"improv_serial does not support the selected logger hardware_uart"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_logger
|
||||
|
||||
@@ -146,13 +146,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _validate_cpu_frequency(config):
|
||||
def _validate_cpu_frequency(config) -> None:
|
||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||
if esp32_config[CONF_CPU_FREQUENCY] != "240MHZ":
|
||||
raise cv.Invalid(
|
||||
"Inkplate requires 240MHz CPU frequency (set in esp32 component)"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _validate_cpu_frequency
|
||||
|
||||
@@ -336,7 +336,7 @@ def _customise_schema(config):
|
||||
CONFIG_SCHEMA = _customise_schema
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
# IT8951 reads from SPI (DevInfo, VCOM, register reads) so MISO is required.
|
||||
spi.final_validate_device_schema("it8951", require_miso=True, require_mosi=True)(
|
||||
config
|
||||
@@ -351,7 +351,6 @@ def _final_validate(config):
|
||||
config[CONF_UPDATE_INTERVAL] = update_interval("never")
|
||||
else:
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -89,8 +89,8 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("kuntze", role="client")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("kuntze", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -84,7 +84,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
def final_validate(config: ConfigType) -> ConfigType:
|
||||
def final_validate(config: ConfigType) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
hub_id = config[CONF_LD6002B_ID]
|
||||
|
||||
@@ -108,8 +108,6 @@ def final_validate(config: ConfigType) -> ConfigType:
|
||||
path=[CONF_WAKE],
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
|
||||
@@ -105,9 +105,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
def final_validate(config: ConfigType) -> ConfigType:
|
||||
def final_validate(config: ConfigType) -> None:
|
||||
if config.get(CONF_AREA_CONFIG) is None:
|
||||
return config
|
||||
return
|
||||
|
||||
full_config = fv.full_config.get()
|
||||
hub_id = config[CONF_LD6002B_ID]
|
||||
@@ -132,8 +132,6 @@ def final_validate(config: ConfigType) -> ConfigType:
|
||||
path=[CONF_AREA_CONFIG],
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ def available_effects_str(effects: list) -> str:
|
||||
return ", ".join(f"'{name}'" for name in available) if available else "none"
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Validate all recorded effect name references against their target lights.
|
||||
|
||||
This runs once per light platform instance. If no light platform is configured,
|
||||
@@ -173,7 +173,7 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
"""
|
||||
data = _get_data()
|
||||
if not data.effect_refs and not data.effect_cycle_refs:
|
||||
return config
|
||||
return
|
||||
|
||||
# Drain the lists so we only validate once even though
|
||||
# FINAL_VALIDATE_SCHEMA runs for each light platform instance.
|
||||
@@ -217,8 +217,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
path=[cv.ROOT_CONFIG_PATH] + ref.component_path,
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ CONF_NONVOLATILE_WRITE_DELAY = "nonvolatile_write_delay"
|
||||
VOLATILE_CHANNELS = ("A", "B", "C", "D")
|
||||
|
||||
|
||||
def _validate_nonvolatile(config):
|
||||
def _validate_nonvolatile(config) -> None:
|
||||
channel = str(config[CONF_CHANNEL])
|
||||
|
||||
# Channels E-H address the nonvolatile registers directly — the mirroring options only
|
||||
@@ -49,7 +49,7 @@ def _validate_nonvolatile(config):
|
||||
f"enabling '{CONF_NONVOLATILE}' or setting '{CONF_NONVOLATILE_WRITE_DELAY}' is only valid for the "
|
||||
f"volatile channels A-D; channels E-H are the nonvolatile registers themselves"
|
||||
)
|
||||
return config
|
||||
return
|
||||
|
||||
config.setdefault(CONF_NONVOLATILE, True)
|
||||
if config[CONF_NONVOLATILE]:
|
||||
@@ -62,7 +62,6 @@ def _validate_nonvolatile(config):
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_NONVOLATILE_WRITE_DELAY}' requires '{CONF_NONVOLATILE}: true'"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
|
||||
@@ -62,7 +62,7 @@ def _consume_mdns_sockets(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def _require_network_interface(config: ConfigType) -> ConfigType:
|
||||
def _require_network_interface(config: ConfigType) -> None:
|
||||
"""Require a network interface for mDNS on Arduino/LEAmDNS platforms.
|
||||
|
||||
On ESP8266 and RP2040 the C++ implementation needs at least one IP state
|
||||
@@ -71,7 +71,7 @@ def _require_network_interface(config: ConfigType) -> ConfigType:
|
||||
that never initializes.
|
||||
"""
|
||||
if config.get(CONF_DISABLED) or not (CORE.is_esp8266 or CORE.is_rp2):
|
||||
return config
|
||||
return
|
||||
full_config = fv.full_config.get()
|
||||
has_wifi = "wifi" in full_config
|
||||
has_ethernet = CORE.is_rp2 and "ethernet" in full_config
|
||||
@@ -81,7 +81,6 @@ def _require_network_interface(config: ConfigType) -> ConfigType:
|
||||
"mdns on this platform requires a network interface — "
|
||||
f"add a {options} component to your configuration."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
|
||||
@@ -175,7 +175,7 @@ def _config_schema(config):
|
||||
return config
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
global_config = full_config.get()
|
||||
|
||||
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
|
||||
@@ -183,7 +183,6 @@ def _final_validate(config):
|
||||
if not requires_buffer(config) and LVGL_DOMAIN not in global_config:
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = _config_schema
|
||||
|
||||
@@ -248,7 +248,7 @@ def _config_schema(config):
|
||||
CONFIG_SCHEMA = _config_schema
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
global_config = full_config.get()
|
||||
|
||||
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
|
||||
@@ -260,7 +260,6 @@ def _final_validate(config):
|
||||
config = spi.final_validate_device_schema(
|
||||
"mipi_rgb", require_miso=False, require_mosi=True
|
||||
)(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -143,11 +143,11 @@ def CONFIG_SCHEMA(config: ConfigType) -> ConfigType:
|
||||
|
||||
|
||||
# Legacy climate-owned hub compatibility. Remove in 2027.2.0.
|
||||
def _legacy_final_validate(config: ConfigType) -> ConfigType:
|
||||
def _legacy_final_validate(config: ConfigType) -> None:
|
||||
if CONF_MITSUBISHI_CN105_ID in config:
|
||||
return config
|
||||
return
|
||||
|
||||
return uart.final_validate_device_schema(
|
||||
uart.final_validate_device_schema(
|
||||
DOMAIN,
|
||||
require_rx=True,
|
||||
require_tx=True,
|
||||
|
||||
@@ -135,10 +135,8 @@ def validate_modbus_register(config):
|
||||
return config
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("modbus_controller", role="client")(
|
||||
config
|
||||
)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("modbus_controller", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -144,8 +144,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("modbus_server", role="server")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("modbus_server", role="server")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -44,10 +44,10 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
if config[CONF_TYPE] != CONF_STATUS:
|
||||
# Only run this validation if a status sensor is being configured
|
||||
return config
|
||||
return
|
||||
full_config = fv.full_config.get()
|
||||
transport_path = full_config.get_path_for_id(config[CONF_TRANSPORT_ID])[:-1]
|
||||
transport_config = full_config.get_config_for_path(transport_path)
|
||||
@@ -56,7 +56,7 @@ def _final_validate(config):
|
||||
for p in transport_config[CONF_PROVIDERS]
|
||||
if p[CONF_NAME] == config[CONF_PROVIDER]
|
||||
):
|
||||
return config
|
||||
return
|
||||
raise cv.Invalid(
|
||||
"Status sensor requires ping-pong to be enabled and the nominated provider to use encryption."
|
||||
)
|
||||
|
||||
@@ -67,7 +67,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Validate the provisioning setup once every component has been processed.
|
||||
|
||||
Sources register during their own config validation, so by final validation
|
||||
@@ -89,7 +89,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
"hardcoding them makes the window pointless.",
|
||||
", ".join(sorted(data.hardcoded_credentials)),
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -98,8 +98,8 @@ async def reset_energy_to_code(config, action_id, template_arg, args):
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("pzemac", role="client")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("pzemac", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -80,8 +80,8 @@ async def reset_energy_to_code(config, action_id, template_arg, args):
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("pzemdc", role="client")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("pzemdc", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -63,7 +63,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
# Validate every configured output speaker can accept the router's format.
|
||||
# Switching to an output that can't reproduce the format the producer is
|
||||
# already sending would otherwise fail silently at runtime.
|
||||
@@ -76,7 +76,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
channels=config[CONF_NUM_CHANNELS],
|
||||
sample_rate=config[CONF_SAMPLE_RATE],
|
||||
)(proxy)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -71,10 +71,9 @@ def validate_connection_slots() -> None:
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
_validate_board(config)
|
||||
validate_connection_slots()
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -148,8 +148,8 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("sdm_meter", role="client")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("sdm_meter", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -63,7 +63,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
# In the default mode setup() writes config commands, so tx is required;
|
||||
# rx_only mode never writes, so tx is optional.
|
||||
uart.final_validate_device_schema(
|
||||
@@ -75,7 +75,6 @@ def _final_validate(config):
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -164,8 +164,8 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return modbus.final_validate_modbus_device("selec_meter", role="client")(config)
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("selec_meter", role="client")(config)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -57,7 +57,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
def _final_validate(config) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
if not any(name in full_config for name in _USB_CLASS_COMPONENTS):
|
||||
raise cv.Invalid(
|
||||
@@ -75,7 +75,6 @@ def _final_validate(config):
|
||||
"USB_SERIAL_JTAG on variants that support it "
|
||||
"(ESP32-S3, ESP32-S31, ESP32-P4, ESP32-H4)"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -193,7 +193,7 @@ def _validate_no_sorting_component(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate_sorting(config: ConfigType) -> ConfigType:
|
||||
def _final_validate_sorting(config: ConfigType) -> None:
|
||||
if (webserver_version := config.get(CONF_VERSION)) != 3:
|
||||
_validate_no_sorting_component(
|
||||
CONF_SORTING_WEIGHT, webserver_version, fv.full_config.get()
|
||||
@@ -201,7 +201,6 @@ def _final_validate_sorting(config: ConfigType) -> ConfigType:
|
||||
_validate_no_sorting_component(
|
||||
CONF_SORTING_GROUP_ID, webserver_version, fv.full_config.get()
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate_sorting
|
||||
|
||||
@@ -102,9 +102,8 @@ def _allocate_blocks() -> None:
|
||||
_get_data().pwm_blocks = pwm_blocks
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
_allocate_blocks()
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -11,7 +11,7 @@ zwave_proxy_ns = cg.esphome_ns.namespace("zwave_proxy")
|
||||
ZWaveProxy = zwave_proxy_ns.class_("ZWaveProxy", cg.Component, uart.UARTDevice)
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
def final_validate(config) -> None:
|
||||
full_config = fv.full_config.get()
|
||||
if (wifi_conf := full_config.get(CONF_WIFI)) and (
|
||||
wifi_conf.get(CONF_POWER_SAVE_MODE).lower() != "none"
|
||||
@@ -20,8 +20,6 @@ def final_validate(config):
|
||||
f"{CONF_WIFI} {CONF_POWER_SAVE_MODE} must be set to 'none' when using Z-Wave proxy"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
|
||||
@@ -88,9 +88,17 @@ std::string str_sprintf(const char *fmt, ...) {
|
||||
|
||||
// --- Base64 helpers ---
|
||||
|
||||
static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
// Map a 6-bit value (0-63) to its base64 character arithmetically.
|
||||
// No lookup table: a table would occupy RAM on ESP8266 (.rodata lives in DRAM there).
|
||||
static inline char base64_char(uint8_t index) {
|
||||
if (index < 26)
|
||||
return 'A' + index;
|
||||
if (index < 52)
|
||||
return 'a' + (index - 26);
|
||||
if (index < 62)
|
||||
return '0' + (index - 52);
|
||||
return index == 62 ? '+' : '/';
|
||||
}
|
||||
|
||||
// Encode 3 input bytes to 4 base64 characters, append 'count' to ret.
|
||||
static inline void base64_encode_triple(const char *char_array_3, int count, std::string &ret) {
|
||||
@@ -101,7 +109,7 @@ static inline void base64_encode_triple(const char *char_array_3, int count, std
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
ret += BASE64_CHARS[static_cast<uint8_t>(char_array_4[j])];
|
||||
ret += base64_char(static_cast<uint8_t>(char_array_4[j]));
|
||||
}
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
||||
|
||||
+12
-12
@@ -579,13 +579,8 @@ int8_t step_to_accuracy_decimals(float step) {
|
||||
return str.length() - dot_pos - 1;
|
||||
}
|
||||
|
||||
// Use C-style string constant to store in ROM instead of RAM (saves 24 bytes)
|
||||
static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
// Helper function to find the index of a base64/base64url character in the lookup table.
|
||||
// Returns the character's position (0-63) if found, or 0 if not found.
|
||||
// Map a base64/base64url character to its 6-bit value (0-63) arithmetically.
|
||||
// No lookup table: a table would occupy RAM on ESP8266 (.rodata lives in DRAM there).
|
||||
// Supports both standard base64 (+/) and base64url (-_) alphabets.
|
||||
// NOTE: This returns 0 for both 'A' (valid base64 char at index 0) and invalid characters.
|
||||
// This is safe because is_base64() is ALWAYS checked before calling this function,
|
||||
@@ -593,13 +588,18 @@ static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// stops processing at the first invalid character due to the is_base64() check in its
|
||||
// while loop condition, making this edge case harmless in practice.
|
||||
static inline uint8_t base64_find_char(char c) {
|
||||
// Handle base64url variants: '-' maps to '+' (index 62), '_' maps to '/' (index 63)
|
||||
if (c == '-')
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0' + 52;
|
||||
// base64url variants: '-' maps to '+' (index 62), '_' maps to '/' (index 63)
|
||||
if (c == '+' || c == '-')
|
||||
return 62;
|
||||
if (c == '_')
|
||||
if (c == '/' || c == '_')
|
||||
return 63;
|
||||
const char *pos = strchr(BASE64_CHARS, c);
|
||||
return pos ? (pos - BASE64_CHARS) : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if character is valid base64 or base64url
|
||||
|
||||
@@ -110,12 +110,12 @@ dependencies:
|
||||
# refuses to build two managed components whose names differ only by
|
||||
# namespace. The Arduino envs get noise-c as a PlatformIO library instead.
|
||||
esphome/noise-c:
|
||||
version: 0.1.18
|
||||
version: 0.1.19
|
||||
rules:
|
||||
- if: "$ESPHOME_ARDUINO_COMPONENT == 0"
|
||||
# Declared even though noise-c depends on it, so that the PlatformIO-library
|
||||
# converter knows to skip the copy esp_wireguard would otherwise pull in.
|
||||
esphome/libsodium:
|
||||
version: 1.10021.2
|
||||
version: 1.10021.3
|
||||
rules:
|
||||
- if: "$ESPHOME_ARDUINO_COMPONENT == 0"
|
||||
|
||||
+3
-3
@@ -78,7 +78,7 @@ lib_deps =
|
||||
${common.lib_deps}
|
||||
; api -- on the ESP-IDF framework this comes from the component registry
|
||||
; instead (see esphome/idf_component.yml), so it is not in [common].
|
||||
esphome/noise-c@0.1.18 ; api
|
||||
esphome/noise-c@0.1.19 ; api
|
||||
SPI ; spi (Arduino built-in)
|
||||
Wire ; i2c (Arduino built-int)
|
||||
heman/AsyncMqttClient-esphome@1.0.0 ; mqtt
|
||||
@@ -246,7 +246,7 @@ lib_deps =
|
||||
${common:idf-component-libs.lib_deps}
|
||||
ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base
|
||||
droscy/esp_wireguard@0.4.5 ; wireguard
|
||||
esphome/noise-c@0.1.18 ; api
|
||||
esphome/noise-c@0.1.19 ; api
|
||||
ESP32Async/AsyncTCP@3.4.5 ; async_tcp
|
||||
DNSServer ; captive_portal
|
||||
heman/AsyncMqttClient-esphome@2.0.0 ; mqtt
|
||||
@@ -643,7 +643,7 @@ build_unflags =
|
||||
extends = common
|
||||
platform = platformio/native
|
||||
lib_deps =
|
||||
esphome/noise-c@0.1.18 ; used by api
|
||||
esphome/noise-c@0.1.19 ; used by api
|
||||
lvgl/lvgl@9.5.0 ; lvgl
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ bleak==3.0.2
|
||||
smpclient==7.2.0
|
||||
requests==2.34.2
|
||||
py7zr==1.1.3
|
||||
platformdirs==4.11.2 # native esp-idf toolchain global cache dir
|
||||
filelock==3.32.2 # inter-process locks (PlatformIO cache heal, git clone cache); >=3.32 for FileLock(fallback_to_soft=...), older versions silently drop the kwarg
|
||||
platformdirs==4.11.3 # native esp-idf toolchain global cache dir
|
||||
filelock==3.32.3 # inter-process locks (PlatformIO cache heal, git clone cache); >=3.32 for FileLock(fallback_to_soft=...), older versions silently drop the kwarg
|
||||
|
||||
# esp-idf >= 5.0 requires this
|
||||
pyparsing >= 3.3.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pylint==4.0.7
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.16.2 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.16.3 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
||||
prek==0.4.13 # also change in .github/workflows/ci.yml when updating
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ def test_final_validate_accepts_supported_idf(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_IDF_VERSION: cv.Version.parse(idf)},
|
||||
)
|
||||
assert _final_validate({}) == {}
|
||||
_final_validate({})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("idf", ["5.0.0", "5.2.2"])
|
||||
|
||||
@@ -371,27 +371,28 @@ def test_migrate_returns_none_for_invalid_legacy_shapes(
|
||||
|
||||
|
||||
def test_validate_image_final_defaults_to_little_endian() -> None:
|
||||
out = validate_image_final({CONF_FILE: "x.png"})
|
||||
assert out[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
|
||||
config = {CONF_FILE: "x.png"}
|
||||
validate_image_final(config)
|
||||
assert config[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
|
||||
|
||||
|
||||
def test_validate_image_final_keeps_little_endian(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
config = {CONF_FILE: "x.png", CONF_BYTE_ORDER: "LITTLE_ENDIAN"}
|
||||
with caplog.at_level(logging.WARNING):
|
||||
out = validate_image_final(
|
||||
{CONF_FILE: "x.png", CONF_BYTE_ORDER: "LITTLE_ENDIAN"}
|
||||
)
|
||||
assert out[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
|
||||
validate_image_final(config)
|
||||
assert config[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
|
||||
assert "big-endian" not in caplog.text
|
||||
|
||||
|
||||
def test_validate_image_final_warns_on_big_endian(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
config = {CONF_FILE: "x.png", CONF_BYTE_ORDER: "BIG_ENDIAN"}
|
||||
with caplog.at_level(logging.WARNING):
|
||||
out = validate_image_final({CONF_FILE: "x.png", CONF_BYTE_ORDER: "BIG_ENDIAN"})
|
||||
assert out[CONF_BYTE_ORDER] == "BIG_ENDIAN"
|
||||
validate_image_final(config)
|
||||
assert config[CONF_BYTE_ORDER] == "BIG_ENDIAN"
|
||||
assert "big-endian" in caplog.text
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ def test_provisioning_accepts_a_registered_source(
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
register_source("network")
|
||||
# Should not raise.
|
||||
assert FINAL_VALIDATE_SCHEMA({}) == {}
|
||||
FINAL_VALIDATE_SCHEMA({})
|
||||
|
||||
|
||||
def test_provisioning_warns_on_hardcoded_credentials(
|
||||
@@ -49,7 +49,7 @@ def test_provisioning_warns_on_hardcoded_credentials(
|
||||
register_source("network")
|
||||
report_hardcoded_credentials("wifi")
|
||||
with caplog.at_level(logging.WARNING):
|
||||
assert FINAL_VALIDATE_SCHEMA({}) == {}
|
||||
FINAL_VALIDATE_SCHEMA({})
|
||||
assert "wifi" in caplog.text
|
||||
assert "credentials" in caplog.text
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_provisioning_no_warning_without_hardcoded_credentials(
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
register_source("network")
|
||||
with caplog.at_level(logging.WARNING):
|
||||
assert FINAL_VALIDATE_SCHEMA({}) == {}
|
||||
FINAL_VALIDATE_SCHEMA({})
|
||||
assert "credentials" not in caplog.text
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/alloc_helpers.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::core::testing {
|
||||
@@ -213,4 +214,70 @@ TEST(BufAppendSepStr, Truncation) {
|
||||
EXPECT_EQ(end - buf, 7);
|
||||
}
|
||||
|
||||
// --- base64 encode/decode ---
|
||||
|
||||
static const char BASE64_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
// Pack 6-bit indices 0..63 into 48 bytes so encoding yields the full alphabet in order
|
||||
TEST(Base64, EncodeProducesCanonicalAlphabet) {
|
||||
uint8_t bytes[48];
|
||||
size_t n = 0;
|
||||
for (uint8_t i = 0; i < 64; i += 4) {
|
||||
bytes[n++] = (i << 2) | ((i + 1) >> 4);
|
||||
bytes[n++] = ((i + 1) & 0x0F) << 4 | ((i + 2) >> 2);
|
||||
bytes[n++] = ((i + 2) & 0x03) << 6 | (i + 3);
|
||||
}
|
||||
std::string encoded = base64_encode(bytes, sizeof(bytes)); // NOLINT(esphome-heap-allocation) - host test
|
||||
EXPECT_EQ(encoded, BASE64_ALPHABET);
|
||||
}
|
||||
|
||||
// Decode the alphabet then re-encode: locks the encode and decode mappings together
|
||||
TEST(Base64, DecodeCanonicalAlphabetRoundTrip) {
|
||||
uint8_t buf[48];
|
||||
size_t len = base64_decode(std::string(BASE64_ALPHABET), buf, sizeof(buf));
|
||||
EXPECT_EQ(len, 48u);
|
||||
std::string reencoded = base64_encode(buf, len); // NOLINT(esphome-heap-allocation) - host test
|
||||
EXPECT_EQ(reencoded, BASE64_ALPHABET);
|
||||
}
|
||||
|
||||
TEST(Base64, DecodeBase64UrlMatchesStandard) {
|
||||
std::string url = BASE64_ALPHABET;
|
||||
for (char &c : url) {
|
||||
if (c == '+')
|
||||
c = '-';
|
||||
if (c == '/')
|
||||
c = '_';
|
||||
}
|
||||
uint8_t standard[48], urlsafe[48];
|
||||
size_t len_standard = base64_decode(std::string(BASE64_ALPHABET), standard, sizeof(standard));
|
||||
size_t len_url = base64_decode(url, urlsafe, sizeof(urlsafe));
|
||||
EXPECT_EQ(len_standard, len_url);
|
||||
EXPECT_EQ(memcmp(standard, urlsafe, len_standard), 0);
|
||||
}
|
||||
|
||||
// RFC 4648 vectors cover both padding cases (len % 3 == 1 and len % 3 == 2)
|
||||
TEST(Base64, Rfc4648Vectors) {
|
||||
const struct {
|
||||
const char *plain;
|
||||
const char *encoded;
|
||||
} vectors[] = {
|
||||
{"", ""},
|
||||
{"f", "Zg=="},
|
||||
{"fo", "Zm8="},
|
||||
{"foo", "Zm9v"},
|
||||
{"foob", "Zm9vYg=="},
|
||||
{"fooba", "Zm9vYmE="},
|
||||
{"foobar", "Zm9vYmFy"},
|
||||
};
|
||||
for (const auto &v : vectors) {
|
||||
const auto *plain = reinterpret_cast<const uint8_t *>(v.plain);
|
||||
std::string encoded = base64_encode(plain, strlen(v.plain)); // NOLINT(esphome-heap-allocation) - host test
|
||||
EXPECT_EQ(encoded, v.encoded);
|
||||
uint8_t buf[8];
|
||||
size_t len = base64_decode(reinterpret_cast<const uint8_t *>(v.encoded), strlen(v.encoded), buf, sizeof(buf));
|
||||
EXPECT_EQ(len, strlen(v.plain));
|
||||
EXPECT_EQ(memcmp(buf, v.plain, len), 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
|
||||
Reference in New Issue
Block a user