From 7957808f00eec1eac78e40cd59dac8815ae7c55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Metrich?= <45318189+FredM67@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:57:58 +0200 Subject: [PATCH 1/6] [emontx] Fix sensor state_class defaults not being applied correctly (#17610) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 --- esphome/components/emontx/sensor/__init__.py | 63 ++++++----- tests/component_tests/emontx/__init__.py | 0 .../emontx/test_sensor_defaults.py | 100 ++++++++++++++++++ tests/components/emontx/test.esp32-idf.yaml | 3 +- tests/components/emontx/test.esp8266-ard.yaml | 3 +- tests/components/emontx/test.rp2040-ard.yaml | 3 +- .../components/emontx/validate.esp32-idf.yaml | 73 +++++++++++++ 7 files changed, 213 insertions(+), 32 deletions(-) create mode 100644 tests/component_tests/emontx/__init__.py create mode 100644 tests/component_tests/emontx/test_sensor_defaults.py create mode 100644 tests/components/emontx/validate.esp32-idf.yaml diff --git a/esphome/components/emontx/sensor/__init__.py b/esphome/components/emontx/sensor/__init__.py index 83a972c5e0..967bc4e699 100644 --- a/esphome/components/emontx/sensor/__init__.py +++ b/esphome/components/emontx/sensor/__init__.py @@ -68,6 +68,7 @@ PATTERN_CONFIGS = { "PULSE": { CONF_UNIT_OF_MEASUREMENT: UNIT_PULSES, CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY, + CONF_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, CONF_ACCURACY_DECIMALS: 0, }, "PF": { @@ -78,12 +79,13 @@ PATTERN_CONFIGS = { }, } -# Create a base schema that's flexible for any tag -BASE_SCHEMA = sensor.sensor_schema( - EmonTxSensor, - state_class=STATE_CLASS_MEASUREMENT, - accuracy_decimals=0, -).extend( +# BASE_SCHEMA intentionally omits state_class and accuracy_decimals defaults. +# Passing them to sensor_schema() would register them via cv.Optional(key, default=...), +# making them always present in the validated config dict and preventing +# apply_tag_defaults from overriding them with the correct per-prefix values. +# They are injected by apply_tag_defaults below, after running through +# sensor.validate_state_class() so the value is code-generation-ready. +BASE_SCHEMA = sensor.sensor_schema(EmonTxSensor).extend( { cv.GenerateID(CONF_EMONTX_ID): cv.use_id(EmonTx), cv.Required(CONF_TAG_NAME): cv.string, @@ -91,34 +93,43 @@ BASE_SCHEMA = sensor.sensor_schema( ) +def _apply_defaults(config: ConfigType, defaults: dict) -> None: + """Inject defaults into config, skipping keys already set by the user. + state_class values are run through validate_state_class so they are + code-generation-ready, matching what sensor_schema() would normally do.""" + for key, value in defaults.items(): + if key not in config: + if key == CONF_STATE_CLASS: + value = sensor.validate_state_class(value) + config[key] = value + + def apply_tag_defaults(config: ConfigType) -> ConfigType: """Apply defaults based on tag prefix if applicable, but don't restrict any tags.""" tag = config[CONF_TAG_NAME] - # Skip if tag is too short - if len(tag) < 2: - return config + if len(tag) >= 2: + tag_upper = tag.upper() - # Check if this tag starts with a known prefix - tag_upper = tag.upper() + for pattern, pattern_config in PATTERN_CONFIGS.items(): + if tag_upper.startswith(pattern): + _apply_defaults(config, pattern_config) + return config - for pattern, pattern_config in PATTERN_CONFIGS.items(): - if tag_upper.startswith(pattern): - # Apply pattern defaults if not overridden by user - for key, value in pattern_config.items(): - if key not in config: - config[key] = value + # Only apply defaults for known prefixes with numeric indices (e.g. E1, V2, T3) + prefix = tag_upper[0] + if prefix in SENSOR_CONFIGS and tag[1:].isdigit(): + _apply_defaults(config, SENSOR_CONFIGS[prefix]) return config - # Only apply defaults for known prefixes with numeric indices - prefix = tag_upper[0] - if prefix in SENSOR_CONFIGS and len(tag) > 1 and tag[1:].isdigit(): - # Apply defaults for known tag types, but only if not overridden by user - defaults = SENSOR_CONFIGS[prefix] - for key, value in defaults.items(): - if key not in config: - config[key] = value - + # Fall back to generic defaults for tags with no known prefix + _apply_defaults( + config, + { + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_ACCURACY_DECIMALS: 0, + }, + ) return config diff --git a/tests/component_tests/emontx/__init__.py b/tests/component_tests/emontx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/emontx/test_sensor_defaults.py b/tests/component_tests/emontx/test_sensor_defaults.py new file mode 100644 index 0000000000..00d24d282e --- /dev/null +++ b/tests/component_tests/emontx/test_sensor_defaults.py @@ -0,0 +1,100 @@ +"""Tests for emontx sensor tag defaults.""" + +import pytest + +from esphome.components import sensor +from esphome.components.emontx.sensor import CONFIG_SCHEMA, apply_tag_defaults +from esphome.const import ( + CONF_ACCURACY_DECIMALS, + CONF_STATE_CLASS, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, +) + + +def _resolve_via_config_schema(tag: str) -> dict: + """Run a minimal config through the real CONFIG_SCHEMA pipeline, the + same path a user's YAML goes through.""" + return CONFIG_SCHEMA( + {"tag_name": tag, "emontx_id": "my_emontx", "name": f"{tag} sensor"} + ) + + +def test_config_schema_applies_tag_default_state_class(): + """If sensor_schema(state_class=...) is reintroduced, the schema-level + default wins over apply_tag_defaults' per-prefix value, and E1 would + resolve to measurement instead of total_increasing. Driving the real + CONFIG_SCHEMA (not just apply_tag_defaults) catches that, since + sensor_schema() runs before apply_tag_defaults in the cv.All() chain. + """ + result = _resolve_via_config_schema("E1") + assert result[CONF_STATE_CLASS] == sensor.validate_state_class( + STATE_CLASS_TOTAL_INCREASING + ) + + +def test_config_schema_applies_tag_default_accuracy_decimals(): + """Same root cause as the state_class regression: reintroducing + sensor_schema(accuracy_decimals=...) would make V1 resolve to the + schema-level default instead of the prefix-specific value of 2. + """ + result = _resolve_via_config_schema("V1") + assert result[CONF_ACCURACY_DECIMALS] == 2 + + +def _make_config(tag: str) -> dict: + """Minimal config dict with only tag_name set — no overrides.""" + return {"tag_name": tag} + + +@pytest.mark.parametrize( + ("tag", "expected_state_class", "expected_decimals"), + [ + # Known numeric-index prefixes + ("E1", STATE_CLASS_TOTAL_INCREASING, 0), + ("E12", STATE_CLASS_TOTAL_INCREASING, 0), + ("P1", STATE_CLASS_MEASUREMENT, 0), + ("V1", STATE_CLASS_MEASUREMENT, 2), + ("I1", STATE_CLASS_MEASUREMENT, 2), + ("T1", STATE_CLASS_MEASUREMENT, 2), + # Known patterns + ("PULSE1", STATE_CLASS_TOTAL_INCREASING, 0), + ("PULSE12", STATE_CLASS_TOTAL_INCREASING, 0), + ("PF1", STATE_CLASS_MEASUREMENT, 2), + # Unknown / free-form tags fall back to generic defaults + ("CUSTOM1", STATE_CLASS_MEASUREMENT, 0), + ("X", STATE_CLASS_MEASUREMENT, 0), + ], +) +def test_apply_tag_defaults(tag, expected_state_class, expected_decimals): + """apply_tag_defaults must inject the correct state_class and accuracy_decimals + for each tag type when no user overrides are present.""" + config = _make_config(tag) + result = apply_tag_defaults(config) + + assert result[CONF_STATE_CLASS] == sensor.validate_state_class(expected_state_class) + assert result[CONF_ACCURACY_DECIMALS] == expected_decimals + + +@pytest.mark.parametrize( + ("tag", "user_state_class", "user_decimals"), + [ + # User overrides must not be clobbered by defaults + ("E1", STATE_CLASS_MEASUREMENT, 3), + ("PULSE1", STATE_CLASS_MEASUREMENT, 1), + ("V1", STATE_CLASS_TOTAL_INCREASING, 0), + ("CUSTOM1", STATE_CLASS_TOTAL_INCREASING, 4), + ], +) +def test_apply_tag_defaults_respects_user_overrides( + tag, user_state_class, user_decimals +): + """apply_tag_defaults must not overwrite values already set by the user.""" + config = _make_config(tag) + config[CONF_STATE_CLASS] = sensor.validate_state_class(user_state_class) + config[CONF_ACCURACY_DECIMALS] = user_decimals + + result = apply_tag_defaults(config) + + assert result[CONF_STATE_CLASS] == sensor.validate_state_class(user_state_class) + assert result[CONF_ACCURACY_DECIMALS] == user_decimals diff --git a/tests/components/emontx/test.esp32-idf.yaml b/tests/components/emontx/test.esp32-idf.yaml index a0784fcd53..e56b1bda5d 100644 --- a/tests/components/emontx/test.esp32-idf.yaml +++ b/tests/components/emontx/test.esp32-idf.yaml @@ -1,4 +1,3 @@ packages: uart_115200: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml - -<<: !include common.yaml + emontx: !include common.yaml diff --git a/tests/components/emontx/test.esp8266-ard.yaml b/tests/components/emontx/test.esp8266-ard.yaml index 80a2cb2fc0..9ec9377437 100644 --- a/tests/components/emontx/test.esp8266-ard.yaml +++ b/tests/components/emontx/test.esp8266-ard.yaml @@ -1,4 +1,3 @@ packages: uart_115200: !include ../../test_build_components/common/uart_115200/esp8266-ard.yaml - -<<: !include common.yaml + emontx: !include common.yaml diff --git a/tests/components/emontx/test.rp2040-ard.yaml b/tests/components/emontx/test.rp2040-ard.yaml index 410c579d4b..6f4952d8e5 100644 --- a/tests/components/emontx/test.rp2040-ard.yaml +++ b/tests/components/emontx/test.rp2040-ard.yaml @@ -1,4 +1,3 @@ packages: uart_115200: !include ../../test_build_components/common/uart_115200/rp2040-ard.yaml - -<<: !include common.yaml + emontx: !include common.yaml diff --git a/tests/components/emontx/validate.esp32-idf.yaml b/tests/components/emontx/validate.esp32-idf.yaml new file mode 100644 index 0000000000..7caee78a07 --- /dev/null +++ b/tests/components/emontx/validate.esp32-idf.yaml @@ -0,0 +1,73 @@ +packages: + uart_115200: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml + emontx: !include common.yaml + +# Validate that each sensor type gets the correct default state_class, +# unit_of_measurement, device_class, and accuracy_decimals when NO overrides +# are provided. The values are intentionally omitted so apply_tag_defaults is +# exercised, not the user-override path. + +sensor: + # Energy sensor (E prefix): expects state_class=total_increasing, unit=Wh, + # device_class=energy, accuracy_decimals=0 + - platform: emontx + tag_name: E1 + name: Energy 1 + emontx_id: test_emontx + + # Power sensor (P prefix): expects state_class=measurement, unit=W, + # device_class=power, accuracy_decimals=0 + - platform: emontx + tag_name: P1 + name: Power 1 + emontx_id: test_emontx + + # Voltage sensor (V prefix): expects state_class=measurement, unit=V, + # device_class=voltage, accuracy_decimals=2 + - platform: emontx + tag_name: V1 + name: Voltage 1 + emontx_id: test_emontx + + # Current sensor (I prefix): expects state_class=measurement, unit=A, + # device_class=current, accuracy_decimals=2 + - platform: emontx + tag_name: I1 + name: Current 1 + emontx_id: test_emontx + + # Temperature sensor (T prefix): expects state_class=measurement, unit=°C, + # device_class=temperature, accuracy_decimals=2 + - platform: emontx + tag_name: T1 + name: Temperature 1 + emontx_id: test_emontx + + # Pulse sensor (PULSE pattern): expects state_class=total_increasing, + # unit=pulses, device_class=energy, accuracy_decimals=0 + - platform: emontx + tag_name: PULSE1 + name: Pulse 1 + emontx_id: test_emontx + + # Power factor sensor (PF pattern): expects state_class=measurement, + # device_class=power_factor, accuracy_decimals=2 + - platform: emontx + tag_name: PF1 + name: Power Factor 1 + emontx_id: test_emontx + + # Unknown tag: no prefix match, falls back to state_class=measurement, + # accuracy_decimals=0 + - platform: emontx + tag_name: CUSTOM1 + name: Custom sensor + emontx_id: test_emontx + + # User override: verify that explicit values are respected and not clobbered + - platform: emontx + tag_name: E2 + name: Energy 2 (user override) + emontx_id: test_emontx + state_class: measurement + accuracy_decimals: 3 From 409d74a48da48ea3152c7d8aedb49f622123782f Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:53:44 -0400 Subject: [PATCH 2/6] [esp32_hosted] Fire on_update_available trigger when update is detected (#18591) --- .../esp32_hosted/update/esp32_hosted_update.cpp | 9 +++++++++ .../esp32_hosted/test-embedded.esp32-p4-idf.yaml | 3 +++ .../components/esp32_hosted/test-http.esp32-p4-idf.yaml | 3 +++ 3 files changed, 15 insertions(+) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index 351b0869b0..4eb5d1745b 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -135,6 +135,10 @@ void Esp32HostedUpdate::setup() { // Publish state this->status_clear_error(); this->publish_state(); + // Defer so the automation runs on the main loop after setup, not during App.setup() + if (this->state_ == update::UPDATE_STATE_AVAILABLE && this->update_available_trigger_) { + this->defer([this]() { this->update_available_trigger_->trigger(this->update_info_); }); + } #else // HTTP mode: check every 10s until network is ready (max 6 attempts) // Only if update interval is > 1 minute to avoid redundant checks @@ -185,6 +189,8 @@ void Esp32HostedUpdate::check() { return; } + const bool was_available = this->state_ == update::UPDATE_STATE_AVAILABLE; + // Compare versions if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == this->update_info_.current_version) { @@ -197,6 +203,9 @@ void Esp32HostedUpdate::check() { this->update_info_.progress = 0.0f; this->status_clear_error(); this->publish_state(); + if (this->state_ == update::UPDATE_STATE_AVAILABLE && !was_available && this->update_available_trigger_) { + this->update_available_trigger_->trigger(this->update_info_); + } #endif } diff --git a/tests/components/esp32_hosted/test-embedded.esp32-p4-idf.yaml b/tests/components/esp32_hosted/test-embedded.esp32-p4-idf.yaml index 9640032b34..5cf33179ba 100644 --- a/tests/components/esp32_hosted/test-embedded.esp32-p4-idf.yaml +++ b/tests/components/esp32_hosted/test-embedded.esp32-p4-idf.yaml @@ -6,3 +6,6 @@ update: type: embedded path: $component_dir/test_firmware.bin sha256: de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31 + on_update_available: + then: + - logger.log: "Coprocessor update available" diff --git a/tests/components/esp32_hosted/test-http.esp32-p4-idf.yaml b/tests/components/esp32_hosted/test-http.esp32-p4-idf.yaml index 17cde0f35d..88b620cfe8 100644 --- a/tests/components/esp32_hosted/test-http.esp32-p4-idf.yaml +++ b/tests/components/esp32_hosted/test-http.esp32-p4-idf.yaml @@ -8,3 +8,6 @@ update: type: http source: https://esphome.github.io/esp-hosted-firmware/manifest/esp32c6.json update_interval: 6h + on_update_available: + then: + - logger.log: "Coprocessor update available" From aa944456e0ab4531d7b9184d5d97de166d522913 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 22 Aug 2026 04:11:52 +1200 Subject: [PATCH 3/6] [core] Add type annotations to component Python (6/11) (#18343) --- esphome/components/ags10/sensor.py | 19 ++++++++++--- esphome/components/at581x/__init__.py | 19 ++++++++++--- esphome/components/at581x/switch/__init__.py | 3 ++- esphome/components/canbus/__init__.py | 20 +++++++++----- esphome/components/daly_bms/__init__.py | 3 ++- esphome/components/daly_bms/binary_sensor.py | 6 +++-- esphome/components/daly_bms/sensor.py | 6 +++-- esphome/components/daly_bms/text_sensor.py | 6 +++-- esphome/components/deep_sleep/__init__.py | 21 +++++++++++---- esphome/components/ds1307/time.py | 19 ++++++++++--- .../components/esp32_ble_tracker/__init__.py | 21 ++++++++++----- esphome/components/ethernet/__init__.py | 27 ++++++++++++------- esphome/components/hdc302x/sensor.py | 23 +++++++++++++--- esphome/components/htu21d/sensor.py | 19 ++++++++++--- esphome/components/ld6002b/__init__.py | 2 +- esphome/components/ld6002b/binary_sensor.py | 3 ++- esphome/components/ld6002b/button/__init__.py | 2 +- esphome/components/ld6002b/number/__init__.py | 2 +- esphome/components/ld6002b/select/__init__.py | 3 ++- esphome/components/ld6002b/sensor.py | 3 ++- esphome/components/ld6002b/switch/__init__.py | 3 ++- esphome/components/ld6002b/text_sensor.py | 3 ++- esphome/components/m5stack_8angle/__init__.py | 3 ++- .../m5stack_8angle/binary_sensor/__init__.py | 3 ++- .../m5stack_8angle/light/__init__.py | 3 ++- .../m5stack_8angle/sensor/__init__.py | 3 ++- esphome/components/modbus/__init__.py | 20 ++++++++------ esphome/components/openthread/__init__.py | 25 +++++++++++------ esphome/components/pulse_counter/sensor.py | 21 ++++++++++----- esphome/components/pulse_meter/sensor.py | 21 ++++++++++----- esphome/components/shelly_dimmer/light.py | 11 ++++---- 31 files changed, 246 insertions(+), 97 deletions(-) diff --git a/esphome/components/ags10/sensor.py b/esphome/components/ags10/sensor.py index 6491d7d810..8606e7c247 100644 --- a/esphome/components/ags10/sensor.py +++ b/esphome/components/ags10/sensor.py @@ -17,6 +17,9 @@ from esphome.const import ( UNIT_OHM, UNIT_PARTS_PER_BILLION, ) +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType CONF_RESISTANCE = "resistance" @@ -62,7 +65,7 @@ CONFIG_SCHEMA = ( FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema("ags10", max_frequency="15khz") -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) @@ -94,7 +97,12 @@ AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value( AGS10_NEW_I2C_ADDRESS_SCHEMA, synchronous=True, ) -async def ags10newi2caddress_to_code(config, action_id, template_arg, args): +async def ags10newi2caddress_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) address = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) @@ -126,7 +134,12 @@ AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema( AGS10_SET_ZERO_POINT_SCHEMA, synchronous=True, ) -async def ags10setzeropoint_to_code(config, action_id, template_arg, args): +async def ags10setzeropoint_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) mode = await cg.templatable( diff --git a/esphome/components/at581x/__init__.py b/esphome/components/at581x/__init__.py index 5031b72cce..193e62f615 100644 --- a/esphome/components/at581x/__init__.py +++ b/esphome/components/at581x/__init__.py @@ -4,6 +4,9 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_FREQUENCY, CONF_ID +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType CODEOWNERS = ["@X-Ryl669"] DEPENDENCIES = ["i2c"] @@ -70,7 +73,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) @@ -91,7 +94,12 @@ AT581XSettingsAction = at581x_ns.class_("AT581XSettingsAction", automation.Actio ), synchronous=True, ) -async def at581x_reset_to_code(config, action_id, template_arg, args): +async def at581x_reset_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) @@ -163,7 +171,12 @@ RADAR_SETTINGS_SCHEMA = cv.Schema( RADAR_SETTINGS_SCHEMA, synchronous=True, ) -async def at581x_settings_to_code(config, action_id, template_arg, args): +async def at581x_settings_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/at581x/switch/__init__.py b/esphome/components/at581x/switch/__init__.py index 8e1b82b356..7e45ed89ec 100644 --- a/esphome/components/at581x/switch/__init__.py +++ b/esphome/components/at581x/switch/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import switch import esphome.config_validation as cv from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI +from esphome.types import ConfigType from .. import CONF_AT581X_ID, AT581XComponent, at581x_ns @@ -22,7 +23,7 @@ CONFIG_SCHEMA = switch.switch_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: at581x_component = await cg.get_variable(config[CONF_AT581X_ID]) s = await switch.new_switch(config) await cg.register_parented(s, config[CONF_AT581X_ID]) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index fcd342ad38..b7de235dd1 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -1,10 +1,13 @@ import re +from typing import Any from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID from esphome.core import CORE, ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType CODEOWNERS = ["@mvturnho", "@danielschramm"] IS_PLATFORM_COMPONENT = True @@ -18,7 +21,7 @@ CONF_BIT_RATE = "bit_rate" CONF_ON_FRAME = "on_frame" -def validate_id(config): +def validate_id(config: ConfigType) -> ConfigType: if CONF_CAN_ID in config: can_id = config[CONF_CAN_ID] id_ext = config[CONF_USE_EXTENDED_ID] @@ -27,7 +30,7 @@ def validate_id(config): return config -def validate_raw_data(value): +def validate_raw_data(value: Any) -> bytes | list: if isinstance(value, str): return value.encode("utf-8") if isinstance(value, list): @@ -71,7 +74,7 @@ CAN_SPEEDS = { } -def get_rate(value): +def get_rate(value: str) -> int: match = re.match(r"(\d+)(?:K(\d+)?)?BPS", value, re.IGNORECASE) if not match: raise ValueError(f"Invalid rate format: {value}") @@ -103,7 +106,7 @@ CANBUS_SCHEMA = cv.Schema( CANBUS_SCHEMA.add_extra(validate_id) -async def setup_canbus_core_(var, config): +async def setup_canbus_core_(var: MockObj, config: ConfigType) -> None: await cg.register_component(var, config) cg.add(var.set_can_id([config[CONF_CAN_ID]])) cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]])) @@ -134,7 +137,7 @@ async def setup_canbus_core_(var, config): ) -async def register_canbus(var, config): +async def register_canbus(var: MockObj, config: ConfigType) -> None: if not CORE.has_id(config[CONF_ID]): var = cg.new_Pvariable(config[CONF_ID], var) await setup_canbus_core_(var, config) @@ -157,7 +160,12 @@ async def register_canbus(var, config): ), synchronous=True, ) -async def canbus_action_to_code(config, action_id, template_arg, args): +async def canbus_action_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_CANBUS_ID]) diff --git a/esphome/components/daly_bms/__init__.py b/esphome/components/daly_bms/__init__.py index 87f00ce507..ba0be4d3a5 100644 --- a/esphome/components/daly_bms/__init__.py +++ b/esphome/components/daly_bms/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ADDRESS, CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@s1lvi0"] MULTI_CONF = True @@ -26,7 +27,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/daly_bms/binary_sensor.py b/esphome/components/daly_bms/binary_sensor.py index 95a2ae3b44..2b6ceffff1 100644 --- a/esphome/components/daly_bms/binary_sensor.py +++ b/esphome/components/daly_bms/binary_sensor.py @@ -1,6 +1,8 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from . import CONF_BMS_DALY_ID, DalyBmsComponent @@ -27,13 +29,13 @@ CONFIG_SCHEMA = cv.All( ) -async def setup_conf(config, key, hub): +async def setup_conf(config: ConfigType, key: str, hub: MockObj) -> None: if sensor_config := config.get(key): var = await binary_sensor.new_binary_sensor(sensor_config) cg.add(getattr(hub, f"set_{key}_binary_sensor")(var)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) for key in TYPES: await setup_conf(config, key, hub) diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py index aa92cfa86a..3e91fb280a 100644 --- a/esphome/components/daly_bms/sensor.py +++ b/esphome/components/daly_bms/sensor.py @@ -23,6 +23,8 @@ from esphome.const import ( UNIT_PERCENT, UNIT_VOLT, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from . import CONF_BMS_DALY_ID, DalyBmsComponent @@ -222,13 +224,13 @@ CONFIG_SCHEMA = cv.All( ) -async def setup_conf(config, key, hub): +async def setup_conf(config: ConfigType, key: str, hub: MockObj) -> None: if sensor_config := config.get(key): sens = await sensor.new_sensor(sensor_config) cg.add(getattr(hub, f"set_{key}_sensor")(sens)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) for key in TYPES: await setup_conf(config, key, hub) diff --git a/esphome/components/daly_bms/text_sensor.py b/esphome/components/daly_bms/text_sensor.py index 9f4e2df85a..1a91081bbf 100644 --- a/esphome/components/daly_bms/text_sensor.py +++ b/esphome/components/daly_bms/text_sensor.py @@ -2,6 +2,8 @@ import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv from esphome.const import CONF_STATUS +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from . import CONF_BMS_DALY_ID, DalyBmsComponent @@ -23,13 +25,13 @@ CONFIG_SCHEMA = cv.All( ) -async def setup_conf(config, key, hub): +async def setup_conf(config: ConfigType, key: str, hub: MockObj) -> None: if sensor_config := config.get(key): sens = await text_sensor.new_text_sensor(sensor_config) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_BMS_DALY_ID]) for key in TYPES: await setup_conf(config, key, hub) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 3b70f947d2..91131a3ed7 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -38,7 +38,8 @@ from esphome.const import ( PLATFORM_NRF52, PlatformFramework, ) -from esphome.core import CORE +from esphome.core import CORE, ID +from esphome.cpp_generator import MockObj, TemplateArgsType from esphome.types import ConfigType WAKEUP_PINS = { @@ -174,7 +175,7 @@ def validate_config(config: ConfigType) -> ConfigType: return config -def _validate_ex1_wakeup_mode(value): +def _validate_ex1_wakeup_mode(value: str) -> str: if value == "ALL_LOW": esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value) if value == "ANY_LOW": @@ -345,7 +346,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -458,7 +459,12 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All( DEEP_SLEEP_ENTER_SCHEMA, synchronous=True, ) -async def deep_sleep_enter_to_code(config, action_id, template_arg, args): +async def deep_sleep_enter_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) if CONF_SLEEP_DURATION in config: @@ -487,7 +493,12 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args): automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA), synchronous=True, ) -async def deep_sleep_action_to_code(config, action_id, template_arg, args): +async def deep_sleep_action_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var diff --git a/esphome/components/ds1307/time.py b/esphome/components/ds1307/time.py index 0e7bb976a2..a3ae3eb5af 100644 --- a/esphome/components/ds1307/time.py +++ b/esphome/components/ds1307/time.py @@ -3,6 +3,9 @@ import esphome.codegen as cg from esphome.components import i2c, time import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType CODEOWNERS = ["@badbadc0ffee"] DEPENDENCIES = ["i2c"] @@ -29,7 +32,12 @@ CONFIG_SCHEMA = time.TIME_SCHEMA.extend( ), synchronous=True, ) -async def ds1307_write_time_to_code(config, action_id, template_arg, args): +async def ds1307_write_time_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var @@ -45,13 +53,18 @@ async def ds1307_write_time_to_code(config, action_id, template_arg, args): ), synchronous=True, ) -async def ds1307_read_time_to_code(config, action_id, template_arg, args): +async def ds1307_read_time_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 28c8c7fcf1..4f6355df70 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -38,7 +38,8 @@ from esphome.const import ( CONF_SERVICE_UUID, CONF_TRIGGER_ID, ) -from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority +from esphome.core import CORE, ID, CoroPriority, TimePeriod, coroutine_with_priority +from esphome.cpp_generator import MockObj, TemplateArgsType from esphome.enum import StrEnum from esphome.types import ConfigType @@ -262,7 +263,7 @@ ESP_BLE_DEVICE_SCHEMA = cv.Schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: # Register the loggers this component needs esp32_ble.register_bt_logger(BTLoggers.BLE_SCAN) @@ -360,7 +361,7 @@ async def to_code(config): # chance to call register_ble_tracker and register_client before the list is checked # and added to the global defines list. @coroutine_with_priority(CoroPriority.FINAL) -async def _add_ble_features(): +async def _add_ble_features() -> None: # Add feature-specific defines based on what's needed required_features = _get_required_features() # Sensors registered through the neutral ble_device_base path (BLEHub) need @@ -389,8 +390,11 @@ ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema( synchronous=True, ) async def esp32_ble_tracker_start_scan_action_to_code( - config, action_id, template_arg, args -): + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) template_ = await cg.templatable(config[CONF_CONTINUOUS], args, cg.bool_) @@ -414,8 +418,11 @@ ESP32_BLE_STOP_SCAN_ACTION_SCHEMA = automation.maybe_simple_id( synchronous=True, ) async def esp32_ble_tracker_stop_scan_action_to_code( - config, action_id, template_arg, args -): + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 7686b64cb4..cd5904f501 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -48,10 +48,12 @@ from esphome.const import ( ) from esphome.core import ( CORE, + ID, CoroPriority, TimePeriodMilliseconds, coroutine_with_priority, ) +from esphome.cpp_generator import MockObj, TemplateArgsType import esphome.final_validate as fv from esphome.types import ConfigType @@ -276,7 +278,7 @@ def _validate_spi_interface(config: ConfigType) -> ConfigType: return config -def _validate(config): +def _validate(config: ConfigType) -> ConfigType: if CONF_USE_ADDRESS not in config: if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) @@ -441,7 +443,7 @@ GENERIC_SCHEMA = cv.All( ) -def _spi_schema(default_clock: str = "26.67MHz", max_clock: int = int(80e6)): +def _spi_schema(default_clock: str = "26.67MHz", max_clock: int = int(80e6)) -> cv.All: return cv.All( BASE_SCHEMA.extend( cv.Schema( @@ -517,7 +519,7 @@ CONFIG_SCHEMA = cv.All( ) -def _final_validate_spi(config): +def _final_validate_spi(config: ConfigType) -> None: if not CORE.is_esp32: return # SPI interface validation is ESP32-only if config[CONF_TYPE] not in SPI_ETHERNET_TYPES: @@ -537,7 +539,7 @@ def _final_validate_spi(config): ) -def manual_ip(config): +def manual_ip(config: ConfigType) -> cg.StructInitializer: return cg.StructInitializer( ManualIP, ("static_ip", ip_address_literal(config[CONF_STATIC_IP])), @@ -548,7 +550,7 @@ def manual_ip(config): ) -def phy_register(address: int, value: int, page: int): +def phy_register(address: int, value: int, page: int) -> cg.StructInitializer: return cg.StructInitializer( PHYRegister, ("address", address), @@ -558,7 +560,7 @@ def phy_register(address: int, value: int, page: int): @coroutine_with_priority(CoroPriority.COMMUNICATION) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) # Apply network priority before register_component (which emits the user's @@ -610,7 +612,7 @@ async def to_code(config): CORE.add_job(final_step) -async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None: +async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: from esphome.components.esp32 import ( add_idf_component, add_idf_sdkconfig_option, @@ -698,7 +700,7 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None: add_idf_component(name=component.name, ref=component.version) -async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None: +async def _to_code_rp2040(var: cg.MockObj, config: ConfigType) -> None: cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN])) @@ -793,7 +795,7 @@ FINAL_VALIDATE_SCHEMA = _final_validate @coroutine_with_priority(CoroPriority.FINAL) -async def final_step(): +async def final_step() -> None: """Final code generation step to configure optional Ethernet features.""" if ip_state_count := CORE.data.get(ETHERNET_IP_STATE_LISTENERS_KEY, 0): cg.add_define("USE_ETHERNET_IP_STATE_LISTENERS") @@ -845,7 +847,12 @@ def _filter_source_files() -> list[str]: FILTER_SOURCE_FILES = _filter_source_files -async def _new_pvariable_to_code(config, id_, template_arg, args): +async def _new_pvariable_to_code( + config: ConfigType, + id_: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: return cg.new_Pvariable(id_, template_arg) diff --git a/esphome/components/hdc302x/sensor.py b/esphome/components/hdc302x/sensor.py index a6265b9b98..6d91c3df7c 100644 --- a/esphome/components/hdc302x/sensor.py +++ b/esphome/components/hdc302x/sensor.py @@ -1,3 +1,5 @@ +from typing import Any + from esphome import automation from esphome.automation import maybe_simple_id import esphome.codegen as cg @@ -16,6 +18,9 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -62,7 +67,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) @@ -86,7 +91,7 @@ HDC302X_HEATER_POWER_MAP = { } -def heater_power_value(value): +def heater_power_value(value: Any) -> cv.Lambda | int: """Accept enum names or raw uint16 values""" if isinstance(value, cv.Lambda): return value @@ -119,7 +124,12 @@ HDC302X_HEATER_ON_ACTION_SCHEMA = maybe_simple_id( HDC302X_HEATER_ON_ACTION_SCHEMA, synchronous=True, ) -async def hdc302x_heater_on_to_code(config, action_id, template_arg, args): +async def hdc302x_heater_on_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) template_ = await cg.templatable(config[CONF_POWER], args, cg.uint16) @@ -135,7 +145,12 @@ async def hdc302x_heater_on_to_code(config, action_id, template_arg, args): HDC302X_ACTION_SCHEMA, synchronous=True, ) -async def hdc302x_heater_off_to_code(config, action_id, template_arg, args): +async def hdc302x_heater_off_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index 8808dc70f5..86dca77725 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -17,6 +17,9 @@ from esphome.const import ( UNIT_EMPTY, UNIT_PERCENT, ) +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -63,7 +66,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) @@ -95,7 +98,12 @@ async def to_code(config): ), synchronous=True, ) -async def set_heater_level_to_code(config, action_id, template_arg, args): +async def set_heater_level_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) level_ = await cg.templatable(config[CONF_LEVEL], args, cg.uint8) @@ -115,7 +123,12 @@ async def set_heater_level_to_code(config, action_id, template_arg, args): ), synchronous=True, ) -async def set_heater_to_code(config, action_id, template_arg, args): +async def set_heater_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) status_ = await cg.templatable(config[CONF_STATUS], args, cg.bool_) diff --git a/esphome/components/ld6002b/__init__.py b/esphome/components/ld6002b/__init__.py index 99f2ead3bb..af1e501a6a 100644 --- a/esphome/components/ld6002b/__init__.py +++ b/esphome/components/ld6002b/__init__.py @@ -60,7 +60,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/ld6002b/binary_sensor.py b/esphome/components/ld6002b/binary_sensor.py index 63f7b40c23..74095d5ded 100644 --- a/esphome/components/ld6002b/binary_sensor.py +++ b/esphome/components/ld6002b/binary_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv from esphome.const import CONF_TARGET, DEVICE_CLASS_OCCUPANCY +from esphome.types import ConfigType from . import LD6002BComponent from .const import AREA_COUNT, CONF_LD6002B_ID, MAX_TARGETS @@ -36,7 +37,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) if target_config := config.get(CONF_TARGET): diff --git a/esphome/components/ld6002b/button/__init__.py b/esphome/components/ld6002b/button/__init__.py index 508d5c2bc6..a664890a86 100644 --- a/esphome/components/ld6002b/button/__init__.py +++ b/esphome/components/ld6002b/button/__init__.py @@ -129,7 +129,7 @@ BUTTON_MAP = { } -async def to_code(config): +async def to_code(config: ConfigType) -> None: for key, button_type in BUTTON_MAP.items(): if button_config := config.get(key): b = cg.new_Pvariable(button_config[CONF_ID], button_type) diff --git a/esphome/components/ld6002b/number/__init__.py b/esphome/components/ld6002b/number/__init__.py index 452e38d6e3..236b049f53 100644 --- a/esphome/components/ld6002b/number/__init__.py +++ b/esphome/components/ld6002b/number/__init__.py @@ -136,7 +136,7 @@ def final_validate(config: ConfigType) -> None: FINAL_VALIDATE_SCHEMA = final_validate -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) for key, number_type, setter, min_value, max_value, step in ( diff --git a/esphome/components/ld6002b/select/__init__.py b/esphome/components/ld6002b/select/__init__.py index 3da647ee2c..7f5e528b84 100644 --- a/esphome/components/ld6002b/select/__init__.py +++ b/esphome/components/ld6002b/select/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import select import esphome.config_validation as cv from esphome.const import CONF_AREA_ID, CONF_SENSITIVITY, ENTITY_CATEGORY_CONFIG +from esphome.types import ConfigType from .. import LD6002BComponent, ld6002b_ns from ..const import CONF_INSTALLATION_MODE, CONF_LD6002B_ID, CONF_TRIGGER_SPEED @@ -64,7 +65,7 @@ SELECT_MAP = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) for key, select_type, setter, options in SELECT_MAP: diff --git a/esphome/components/ld6002b/sensor.py b/esphome/components/ld6002b/sensor.py index 3aedaf9fdd..cceefb3837 100644 --- a/esphome/components/ld6002b/sensor.py +++ b/esphome/components/ld6002b/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_METER, ) +from esphome.types import ConfigType from . import LD6002BComponent from .const import ( @@ -150,7 +151,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) if target_count_config := config.get(CONF_TARGET_COUNT): diff --git a/esphome/components/ld6002b/switch/__init__.py b/esphome/components/ld6002b/switch/__init__.py index d27baa87fe..a414308b65 100644 --- a/esphome/components/ld6002b/switch/__init__.py +++ b/esphome/components/ld6002b/switch/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import switch import esphome.config_validation as cv from esphome.const import DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG +from esphome.types import ConfigType from .. import LD6002BComponent, ld6002b_ns from ..const import ( @@ -46,7 +47,7 @@ CONFIG_SCHEMA = cv.Schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) for key, switch_type, setter in ( diff --git a/esphome/components/ld6002b/text_sensor.py b/esphome/components/ld6002b/text_sensor.py index a18d387437..0e8e2e80e7 100644 --- a/esphome/components/ld6002b/text_sensor.py +++ b/esphome/components/ld6002b/text_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC +from esphome.types import ConfigType from . import LD6002BComponent from .const import CONF_LD6002B_ID, CONF_OTA_VERSION, CONF_WORK_MODE @@ -21,7 +22,7 @@ CONFIG_SCHEMA = cv.Schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) if work_mode_config := config.get(CONF_WORK_MODE): sens = await text_sensor.new_text_sensor(work_mode_config) diff --git a/esphome/components/m5stack_8angle/__init__.py b/esphome/components/m5stack_8angle/__init__.py index a1c197b381..6404bcf64c 100644 --- a/esphome/components/m5stack_8angle/__init__.py +++ b/esphome/components/m5stack_8angle/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@rnauber"] @@ -26,7 +27,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(i2c.i2c_device_schema(0x43)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/m5stack_8angle/binary_sensor/__init__.py b/esphome/components/m5stack_8angle/binary_sensor/__init__.py index 22ab73e901..09398876d4 100644 --- a/esphome/components/m5stack_8angle/binary_sensor/__init__.py +++ b/esphome/components/m5stack_8angle/binary_sensor/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv +from esphome.types import ConfigType from .. import CONF_M5STACK_8ANGLE_ID, M5Stack8AngleComponent, m5stack_8angle_ns @@ -22,7 +23,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_M5STACK_8ANGLE_ID]) sens = await binary_sensor.new_binary_sensor(config) cg.add(sens.set_parent(hub)) diff --git a/esphome/components/m5stack_8angle/light/__init__.py b/esphome/components/m5stack_8angle/light/__init__.py index 806ecaabf4..5c4863acf7 100644 --- a/esphome/components/m5stack_8angle/light/__init__.py +++ b/esphome/components/m5stack_8angle/light/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import light import esphome.config_validation as cv from esphome.const import CONF_OUTPUT_ID +from esphome.types import ConfigType from .. import CONF_M5STACK_8ANGLE_ID, M5Stack8AngleComponent, m5stack_8angle_ns @@ -21,7 +22,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_M5STACK_8ANGLE_ID]) lights = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await light.register_light(lights, config) diff --git a/esphome/components/m5stack_8angle/sensor/__init__.py b/esphome/components/m5stack_8angle/sensor/__init__.py index 2132eaa4c2..87d1425241 100644 --- a/esphome/components/m5stack_8angle/sensor/__init__.py +++ b/esphome/components/m5stack_8angle/sensor/__init__.py @@ -8,6 +8,7 @@ from esphome.const import ( ICON_ROTATE_RIGHT, STATE_CLASS_MEASUREMENT, ) +from esphome.types import ConfigType from .. import ( CONF_M5STACK_8ANGLE_ID, @@ -55,7 +56,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await cg.register_parented(var, config[CONF_M5STACK_8ANGLE_ID]) diff --git a/esphome/components/modbus/__init__.py b/esphome/components/modbus/__init__.py index 58bd0f65dc..a98591c6bc 100644 --- a/esphome/components/modbus/__init__.py +++ b/esphome/components/modbus/__init__.py @@ -8,8 +8,10 @@ import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ADDRESS, CONF_DISABLE_CRC, CONF_FLOW_CONTROL_PIN, CONF_ID +from esphome.cpp_generator import MockObj from esphome.cpp_helpers import gpio_pin_expression import esphome.final_validate as fv +from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -84,7 +86,7 @@ CONFIG_SCHEMA = cv.typed_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: cg.add_global(modbus_ns.using) var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -112,7 +114,9 @@ def _validate_server_address(value: Any) -> int: return address -def modbus_device_schema(default_address, role: Literal["client", "server"] = "client"): +def modbus_device_schema( + default_address: int | None, role: Literal["client", "server"] = "client" +) -> cv.Schema: hub_type = ModbusClient if role == "client" else ModbusServer address_validator = _validate_server_address if role == "server" else cv.hex_uint8_t schema = { @@ -127,14 +131,14 @@ def modbus_device_schema(default_address, role: Literal["client", "server"] = "c def final_validate_modbus_device( name: str, *, role: Literal["server", "client"] | None = None -): - def validate_role(value): +) -> cv.Schema: + def validate_role(value: str) -> str: assert role in MODBUS_ROLES if value != role: raise cv.Invalid(f"Component {name} requires role to be {role}") return value - def validate_hub(hub_config): + def validate_hub(hub_config: ConfigType) -> ConfigType: hub_schema = {} if role is not None: hub_schema[cv.Required(CONF_ROLE)] = validate_role @@ -147,19 +151,19 @@ def final_validate_modbus_device( ) -async def register_modbus_client_device(var, config): +async def register_modbus_client_device(var: MockObj, config: ConfigType) -> None: parent = await cg.get_variable(config[CONF_MODBUS_ID]) cg.add(var.set_parent(parent)) cg.add(var.set_address(config[CONF_ADDRESS])) -async def register_modbus_server_device(var, config): +async def register_modbus_server_device(var: MockObj, config: ConfigType) -> None: parent = await cg.get_variable(config[CONF_MODBUS_ID]) cg.add(var.set_address(config[CONF_ADDRESS])) cg.add(parent.register_device(var)) -async def register_modbus_device(var, config): +async def register_modbus_device(var: MockObj, config: ConfigType) -> None: # Remove before 2026.12.0 _LOGGER.warning( "'register_modbus_device' is deprecated, use 'register_modbus_client_device' " diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 4018ad81e7..ab69f5d9ae 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -1,3 +1,5 @@ +from typing import Any + from esphome import automation import esphome.codegen as cg from esphome.components.esp32 import ( @@ -31,10 +33,12 @@ from esphome.const import ( ) from esphome.core import ( CORE, + ID, CoroPriority, TimePeriodMilliseconds, coroutine_with_priority, ) +from esphome.cpp_generator import MockObj, TemplateArgsType import esphome.final_validate as fv from esphome.types import ConfigType @@ -76,7 +80,7 @@ CONF_DEVICE_TYPES = [ ] -def _validate_txpower(value): +def _validate_txpower(value: Any) -> int | float: if CORE.is_esp32: variant = get_esp32_variant() @@ -90,7 +94,7 @@ def _validate_txpower(value): return value # Unsupported, fail later with clear error -def set_sdkconfig_options(config): +def set_sdkconfig_options(config: ConfigType) -> None: # and expose options for using SPI/UART RCPs add_idf_sdkconfig_option("CONFIG_IEEE802154_ENABLED", True) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_RADIO_NATIVE", True) @@ -180,7 +184,7 @@ def _validate(config: ConfigType) -> ConfigType: return config -def _require_vfs_select(config): +def _require_vfs_select(config: ConfigType) -> ConfigType: """Register VFS select requirement during config validation.""" # OpenThread uses esp_vfs_eventfd which requires VFS select support (ESP32 only) if CORE.is_esp32: @@ -188,7 +192,7 @@ def _require_vfs_select(config): return config -def _validate_platform(config): +def _validate_platform(config: ConfigType) -> ConfigType: if CORE.using_zephyr: return config return only_on_variant( @@ -203,7 +207,7 @@ def _validate_platform(config): )(config) -def _validate_tlv_hex(value): +def _validate_tlv_hex(value: Any) -> str: s = cv.string_strict(value) if len(s) % 2 != 0: raise cv.Invalid("TLV must have an even number of hex characters") @@ -242,7 +246,7 @@ CONFIG_SCHEMA = cv.All( ) -def _final_validate(_): +def _final_validate(_: ConfigType) -> None: full_config = fv.full_config.get() network_config = full_config.get("network", {}) if not network_config.get(CONF_ENABLE_IPV6, False): @@ -274,7 +278,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( @coroutine_with_priority(CoroPriority.COMMUNICATION) -async def to_code(config): +async def to_code(config: ConfigType) -> None: # Re-enable openthread IDF component (excluded by default) if CORE.is_esp32: include_builtin_idf_component("openthread") @@ -339,7 +343,12 @@ POLL_PERIOD_ACTION_SCHEMA = automation.maybe_conf( POLL_PERIOD_ACTION_SCHEMA, synchronous=True, ) -async def openthread_poll_period_action_to_code(config, action_id, template_arg, args): +async def openthread_poll_period_action_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) template_ = await cg.templatable(config[CONF_POLL_PERIOD], args, cg.uint32) diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 3326745846..7c5a0590d7 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -1,3 +1,5 @@ +from typing import Any + from esphome import automation, pins import esphome.codegen as cg from esphome.components import sensor @@ -19,7 +21,9 @@ from esphome.const import ( UNIT_PULSES, UNIT_PULSES_PER_MINUTE, ) -from esphome.core import CORE +from esphome.core import CORE, ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType CONF_USE_PCNT = "use_pcnt" @@ -42,7 +46,7 @@ SetTotalPulsesAction = pulse_counter_ns.class_( ) -def validate_internal_filter(value): +def validate_internal_filter(value: ConfigType) -> ConfigType: use_pcnt = value.get(CONF_USE_PCNT) if CORE.is_esp8266 and use_pcnt: raise cv.Invalid( @@ -63,7 +67,7 @@ def validate_internal_filter(value): return value -def validate_pulse_counter_pin(value): +def validate_pulse_counter_pin(value: Any) -> ConfigType: value = pins.internal_gpio_input_pin_schema(value) if CORE.is_esp8266 and value[CONF_NUMBER] >= 16: raise cv.Invalid( @@ -72,7 +76,7 @@ def validate_pulse_counter_pin(value): return value -def validate_count_mode(value): +def validate_count_mode(value: ConfigType) -> ConfigType: rising_edge = value[CONF_RISING_EDGE] falling_edge = value[CONF_FALLING_EDGE] if rising_edge == "DISABLE" and falling_edge == "DISABLE": @@ -126,7 +130,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: use_pcnt = config.get(CONF_USE_PCNT) if CORE.is_esp32 and use_pcnt: include_builtin_idf_component("esp_driver_pcnt") @@ -157,7 +161,12 @@ async def to_code(config): ), synchronous=True, ) -async def set_total_action_to_code(config, action_id, template_arg, args): +async def set_total_action_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint32) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index ab3dd2a249..9bda891efc 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -1,3 +1,5 @@ +from typing import Any + from esphome import automation, pins import esphome.codegen as cg from esphome.components import sensor @@ -17,7 +19,9 @@ from esphome.const import ( UNIT_PULSES, UNIT_PULSES_PER_MINUTE, ) -from esphome.core import CORE +from esphome.core import CORE, ID, TimePeriodMicroseconds +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType CODEOWNERS = ["@stevebaxter", "@cstaahl", "@TrentHouliston"] @@ -37,18 +41,18 @@ FILTER_MODES = { SetTotalPulsesAction = pulse_meter_ns.class_("SetTotalPulsesAction", automation.Action) -def validate_internal_filter(value): +def validate_internal_filter(value: Any) -> TimePeriodMicroseconds: return cv.positive_time_period_microseconds(value) -def validate_timeout(value): +def validate_timeout(value: Any) -> TimePeriodMicroseconds: value = cv.positive_time_period_microseconds(value) if value.total_minutes > 70: raise cv.Invalid("Maximum timeout is 70 minutes") return value -def validate_pulse_meter_pin(value): +def validate_pulse_meter_pin(value: Any) -> ConfigType: value = pins.internal_gpio_input_pin_schema(value) if CORE.is_esp8266 and value[CONF_NUMBER] >= 16: raise cv.Invalid( @@ -81,7 +85,7 @@ CONFIG_SCHEMA = sensor.sensor_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) @@ -107,7 +111,12 @@ async def to_code(config): ), synchronous=True, ) -async def set_total_action_to_code(config, action_id, template_arg, args): +async def set_total_action_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint32) diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py index dd99fcbc90..c166076e0f 100644 --- a/esphome/components/shelly_dimmer/light.py +++ b/esphome/components/shelly_dimmer/light.py @@ -1,6 +1,7 @@ import hashlib from pathlib import Path import re +from typing import Any from esphome import external_files, pins import esphome.codegen as cg @@ -66,7 +67,7 @@ KNOWN_FIRMWARE = { } -def parse_firmware_version(value): +def parse_firmware_version(value: str) -> tuple[int, int]: match = re.fullmatch(r"(\d+)\.(\d+)", value) if match is None: raise ValueError(f"Not a valid version number {value}") @@ -154,7 +155,7 @@ def _extract_firmware_ref(entry: ConfigType) -> RemoteFile | None: PREFETCH_FILES = external_files.single_stage_prefetch(_extract_firmware_ref) -def validate_firmware(value): +def validate_firmware(value: ConfigType) -> ConfigType: config = value.copy() if CONF_URL not in config: try: @@ -167,14 +168,14 @@ def validate_firmware(value): return config -def validate_sha256(value): +def validate_sha256(value: Any) -> str: value = cv.string(value) if not re.fullmatch(r"[0-9a-fA-F]{64}", value): raise ValueError(f"Not a valid SHA256 hex string: {value}") return value -def validate_version(value): +def validate_version(value: str) -> str: parse_firmware_version(value) return value @@ -231,7 +232,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: fw_hex = get_firmware(config[CONF_FIRMWARE]) fw_major, fw_minor = parse_firmware_version(config[CONF_FIRMWARE][CONF_VERSION]) From 00cffa09a2491be8a39ffd1a62d2c6355bedc61c Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Fri, 21 Aug 2026 14:05:07 -0400 Subject: [PATCH 4/6] [sendspin] Convert tests to package-style includes (#18588) --- tests/components/sendspin/common-action.yaml | 2 +- tests/components/sendspin/common-ethernet.yaml | 5 +++++ tests/components/sendspin/common-hub.yaml | 6 ++++++ tests/components/sendspin/common-media_player.yaml | 3 ++- tests/components/sendspin/common-media_source.yaml | 3 ++- tests/components/sendspin/common-sensor.yaml | 3 ++- tests/components/sendspin/common-text_sensor.yaml | 3 ++- tests/components/sendspin/common.yaml | 10 +++------- tests/components/sendspin/test-action.esp32-idf.yaml | 3 ++- .../components/sendspin/test-ethernet.esp32-idf.yaml | 11 ++--------- .../sendspin/test-media_player.esp32-idf.yaml | 3 ++- .../sendspin/test-media_source.esp32-idf.yaml | 3 ++- tests/components/sendspin/test-sensor.esp32-idf.yaml | 3 ++- .../sendspin/test-text_sensor.esp32-idf.yaml | 3 ++- tests/components/sendspin/test.esp32-idf.yaml | 3 ++- 15 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 tests/components/sendspin/common-ethernet.yaml create mode 100644 tests/components/sendspin/common-hub.yaml diff --git a/tests/components/sendspin/common-action.yaml b/tests/components/sendspin/common-action.yaml index 16f19ad7d1..1bba06ab46 100644 --- a/tests/components/sendspin/common-action.yaml +++ b/tests/components/sendspin/common-action.yaml @@ -1,6 +1,6 @@ # `sendspin.switch` action enables the controller role, so we use a standalone test packages: - base: !include common.yaml + sendspin: !include common.yaml wifi: on_connect: diff --git a/tests/components/sendspin/common-ethernet.yaml b/tests/components/sendspin/common-ethernet.yaml new file mode 100644 index 0000000000..276163cda1 --- /dev/null +++ b/tests/components/sendspin/common-ethernet.yaml @@ -0,0 +1,5 @@ +packages: + sendspin_hub: !include common-hub.yaml + +ethernet: + type: OPENETH diff --git a/tests/components/sendspin/common-hub.yaml b/tests/components/sendspin/common-hub.yaml new file mode 100644 index 0000000000..7a6a9ffd4f --- /dev/null +++ b/tests/components/sendspin/common-hub.yaml @@ -0,0 +1,6 @@ +psram: + mode: quad + +sendspin: + id: sendspin_hub_id + task_stack_in_psram: true diff --git a/tests/components/sendspin/common-media_player.yaml b/tests/components/sendspin/common-media_player.yaml index d3792cf470..afb8b992f3 100644 --- a/tests/components/sendspin/common-media_player.yaml +++ b/tests/components/sendspin/common-media_player.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + sendspin: !include common.yaml media_player: - platform: sendspin diff --git a/tests/components/sendspin/common-media_source.yaml b/tests/components/sendspin/common-media_source.yaml index 5b33a54647..1977b79c04 100644 --- a/tests/components/sendspin/common-media_source.yaml +++ b/tests/components/sendspin/common-media_source.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + sendspin: !include common.yaml media_source: - platform: sendspin diff --git a/tests/components/sendspin/common-sensor.yaml b/tests/components/sendspin/common-sensor.yaml index 6d9745cff9..6467e38b90 100644 --- a/tests/components/sendspin/common-sensor.yaml +++ b/tests/components/sendspin/common-sensor.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + sendspin: !include common.yaml sensor: - platform: sendspin diff --git a/tests/components/sendspin/common-text_sensor.yaml b/tests/components/sendspin/common-text_sensor.yaml index fc6a56a21a..23111e8d37 100644 --- a/tests/components/sendspin/common-text_sensor.yaml +++ b/tests/components/sendspin/common-text_sensor.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + sendspin: !include common.yaml text_sensor: - platform: sendspin diff --git a/tests/components/sendspin/common.yaml b/tests/components/sendspin/common.yaml index 9d7da76758..980635b4e3 100644 --- a/tests/components/sendspin/common.yaml +++ b/tests/components/sendspin/common.yaml @@ -1,9 +1,5 @@ +packages: + sendspin_hub: !include common-hub.yaml + wifi: ap: - -psram: - mode: quad - -sendspin: - id: sendspin_hub_id - task_stack_in_psram: true diff --git a/tests/components/sendspin/test-action.esp32-idf.yaml b/tests/components/sendspin/test-action.esp32-idf.yaml index 70a7ee1bad..080eb59034 100644 --- a/tests/components/sendspin/test-action.esp32-idf.yaml +++ b/tests/components/sendspin/test-action.esp32-idf.yaml @@ -1 +1,2 @@ -<<: !include common-action.yaml +packages: + sendspin: !include common-action.yaml diff --git a/tests/components/sendspin/test-ethernet.esp32-idf.yaml b/tests/components/sendspin/test-ethernet.esp32-idf.yaml index 069e397d99..09a951d211 100644 --- a/tests/components/sendspin/test-ethernet.esp32-idf.yaml +++ b/tests/components/sendspin/test-ethernet.esp32-idf.yaml @@ -1,9 +1,2 @@ -ethernet: - type: OPENETH - -psram: - mode: quad - -sendspin: - id: sendspin_hub_id - task_stack_in_psram: true +packages: + sendspin: !include common-ethernet.yaml diff --git a/tests/components/sendspin/test-media_player.esp32-idf.yaml b/tests/components/sendspin/test-media_player.esp32-idf.yaml index cbbdb07c77..bcd4062bbe 100644 --- a/tests/components/sendspin/test-media_player.esp32-idf.yaml +++ b/tests/components/sendspin/test-media_player.esp32-idf.yaml @@ -1 +1,2 @@ -<<: !include common-media_player.yaml +packages: + sendspin: !include common-media_player.yaml diff --git a/tests/components/sendspin/test-media_source.esp32-idf.yaml b/tests/components/sendspin/test-media_source.esp32-idf.yaml index 47aeb2257c..faadccb06d 100644 --- a/tests/components/sendspin/test-media_source.esp32-idf.yaml +++ b/tests/components/sendspin/test-media_source.esp32-idf.yaml @@ -1 +1,2 @@ -<<: !include common-media_source.yaml +packages: + sendspin: !include common-media_source.yaml diff --git a/tests/components/sendspin/test-sensor.esp32-idf.yaml b/tests/components/sendspin/test-sensor.esp32-idf.yaml index f9127d47bc..1646902ca3 100644 --- a/tests/components/sendspin/test-sensor.esp32-idf.yaml +++ b/tests/components/sendspin/test-sensor.esp32-idf.yaml @@ -1 +1,2 @@ -<<: !include common-sensor.yaml +packages: + sendspin: !include common-sensor.yaml diff --git a/tests/components/sendspin/test-text_sensor.esp32-idf.yaml b/tests/components/sendspin/test-text_sensor.esp32-idf.yaml index 8998b8896e..69cf8e63fb 100644 --- a/tests/components/sendspin/test-text_sensor.esp32-idf.yaml +++ b/tests/components/sendspin/test-text_sensor.esp32-idf.yaml @@ -1 +1,2 @@ -<<: !include common-text_sensor.yaml +packages: + sendspin: !include common-text_sensor.yaml diff --git a/tests/components/sendspin/test.esp32-idf.yaml b/tests/components/sendspin/test.esp32-idf.yaml index dade44d145..36667f7fae 100644 --- a/tests/components/sendspin/test.esp32-idf.yaml +++ b/tests/components/sendspin/test.esp32-idf.yaml @@ -1 +1,2 @@ -<<: !include common.yaml +packages: + sendspin: !include common.yaml From abc9098bd833ca2186b4dc0ec59bf32d049d862d Mon Sep 17 00:00:00 2001 From: "esphome[bot]" <115708604+esphome[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:05:00 -0500 Subject: [PATCH 5/6] Bump bundled esphome-device-builder to 1.12.3 (#18601) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2bbe5331e5..4cde6505b3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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.12.2 +RUN uv pip install --no-cache-dir esphome-device-builder==1.12.3 RUN \ platformio settings set enable_telemetry No \ From 11ea819bc7728d72586f34f381de3c57d1584ff5 Mon Sep 17 00:00:00 2001 From: "esphome[bot]" <115708604+esphome[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:36:47 -0500 Subject: [PATCH 6/6] Bump aioesphomeapi from 45.12.0 to 45.13.1 (#18600) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 740a8c1a79..3362e43239 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.19 esptool==5.3.1 click==8.3.3 -aioesphomeapi==45.12.0 +aioesphomeapi==45.13.1 aiohappyeyeballs==2.7.1 # Happy Eyeballs for requests downloads; already pulled in by aioesphomeapi zeroconf==0.150.0 puremagic==2.2.0