From c859c347cb776bce4e296c22374e2fd0606ba7a1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 9 Oct 2026 09:24:32 -1000 Subject: [PATCH] [core] Pass a single condition to actions directly instead of wrapping it in a group (#19223) --- esphome/automation.py | 57 ++----------------- tests/component_tests/automation/__init__.py | 0 .../automation/config/condition_groups.yaml | 44 ++++++++++++++ .../automation/test_condition_groups.py | 25 ++++++++ 4 files changed, 75 insertions(+), 51 deletions(-) create mode 100644 tests/component_tests/automation/__init__.py create mode 100644 tests/component_tests/automation/config/condition_groups.yaml create mode 100644 tests/component_tests/automation/test_condition_groups.py diff --git a/esphome/automation.py b/esphome/automation.py index 645fe566d4..859ec2f1f3 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -699,52 +699,20 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition) @register_condition("and", AndCondition, validate_condition_list) -async def and_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - -@register_condition("or", OrCondition, validate_condition_list) -async def or_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - @register_condition("all", AndCondition, validate_condition_list) -async def all_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - +@register_condition("or", OrCondition, validate_condition_list) @register_condition("any", OrCondition, validate_condition_list) -async def any_condition_to_code( +@register_condition("xor", XorCondition, validate_condition_list) +async def condition_group_to_code( config: ConfigType, condition_id: ID, template_arg: cg.TemplateArguments, args: TemplateArgsType, ) -> MockObj: conditions = await build_condition_list(config, template_arg, args) + # A group of one is that condition for and, or and xor; skip the wrapper. + if len(conditions) == 1: + return conditions[0] return cg.new_Pvariable( condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions ) @@ -761,19 +729,6 @@ async def not_condition_to_code( return cg.new_Pvariable(condition_id, template_arg, condition) -@register_condition("xor", XorCondition, validate_condition_list) -async def xor_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - @register_condition("lambda", LambdaCondition, cv.returning_lambda) async def lambda_condition_to_code( config: ConfigType, diff --git a/tests/component_tests/automation/__init__.py b/tests/component_tests/automation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/automation/config/condition_groups.yaml b/tests/component_tests/automation/config/condition_groups.yaml new file mode 100644 index 0000000000..9624531862 --- /dev/null +++ b/tests/component_tests/automation/config/condition_groups.yaml @@ -0,0 +1,44 @@ +--- +esphome: + name: test + +esp32: + board: nodemcu-32s + +logger: + +binary_sensor: + - platform: template + id: single_and + on_press: + - if: + condition: + - lambda: return true; + then: + - logger.log: single and + - platform: template + id: single_or + on_press: + - if: + any: + - lambda: return true; + then: + - logger.log: single or + - platform: template + id: double_and + on_press: + - if: + condition: + - lambda: return true; + - lambda: return false; + then: + - logger.log: double and + - platform: template + id: single_xor + on_press: + - if: + condition: + xor: + - lambda: return true; + then: + - logger.log: single xor diff --git a/tests/component_tests/automation/test_condition_groups.py b/tests/component_tests/automation/test_condition_groups.py new file mode 100644 index 0000000000..effe7b5b0b --- /dev/null +++ b/tests/component_tests/automation/test_condition_groups.py @@ -0,0 +1,25 @@ +"""Tests for and/or condition group codegen.""" + +from collections.abc import Callable +from pathlib import Path +import re + + +def test_single_condition_groups_are_unwrapped( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """A group of one condition is passed to the action directly.""" + main_cpp = generate_main(component_config_path("condition_groups.yaml")) + + assert "IfAction(lambdacondition_id);" in main_cpp + assert "IfAction(lambdacondition_id_2);" in main_cpp + group = re.search( + r"new\((\w+)\) AndCondition<2>\(\{lambdacondition_id_3, lambdacondition_id_4\}\);", + main_cpp, + ) + assert group is not None + assert f"IfAction({group.group(1)});" in main_cpp + # xor of one condition equals that condition, so it is unwrapped too. + assert "IfAction(lambdacondition_id_5);" in main_cpp + assert "XorCondition<" not in main_cpp