diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index c0bbf51138a..f005c1dfce2 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -870,7 +870,7 @@ async def _async_process_if( for index, check in enumerate(checks): try: with trace_path(["condition", str(index)]): - if not check(hass, variables): + if check(hass, variables) is False: return False except ConditionError as ex: errors.append( diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index d07ddcb42a9..56930a4784e 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -10,7 +10,7 @@ import functools as ft import logging import re import sys -from typing import Any, cast +from typing import Any, Optional, cast from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import condition as device_condition @@ -80,7 +80,7 @@ INPUT_ENTITY_ID = re.compile( r"^input_(?:select|text|number|boolean|datetime)\.(?!.+__)(?!_)[\da-z_]+(? TraceElement: @@ -139,7 +139,7 @@ def trace_condition_function(condition: ConditionCheckerType) -> ConditionChecke """Wrap a condition function to enable basic tracing.""" @ft.wraps(condition) - def wrapper(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: + def wrapper(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool | None: """Trace condition.""" with trace_condition(variables): result = condition(hass, variables) @@ -173,9 +173,9 @@ async def async_from_config( @trace_condition_function def disabled_condition( hass: HomeAssistant, variables: TemplateVarsType = None - ) -> bool: - """Condition not enabled, will always pass.""" - return True + ) -> bool | None: + """Condition not enabled, will act as if it didn't exist.""" + return None return disabled_condition @@ -204,7 +204,7 @@ async def async_and_from_config( for index, check in enumerate(checks): try: with trace_path(["conditions", str(index)]): - if not check(hass, variables): + if check(hass, variables) is False: return False except ConditionError as ex: errors.append( @@ -235,7 +235,7 @@ async def async_or_from_config( for index, check in enumerate(checks): try: with trace_path(["conditions", str(index)]): - if check(hass, variables): + if check(hass, variables) is True: return True except ConditionError as ex: errors.append( diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 65db1291f9d..4d779a1a4d2 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -3291,7 +3291,7 @@ async def test_platform_async_validate_condition_config(hass): async def test_disabled_condition(hass: HomeAssistant) -> None: - """Test a disabled condition always passes.""" + """Test a disabled condition returns none.""" config = { "enabled": False, "condition": "state", @@ -3303,8 +3303,138 @@ async def test_disabled_condition(hass: HomeAssistant) -> None: test = await condition.async_from_config(hass, config) hass.states.async_set("binary_sensor.test", "on") - assert test(hass) + assert test(hass) is None # Still passses, condition is not enabled hass.states.async_set("binary_sensor.test", "off") + assert test(hass) is None + + +async def test_and_condition_with_disabled_condition(hass): + """Test the 'and' condition with one of the conditions disabled.""" + config = { + "alias": "And Condition", + "condition": "and", + "conditions": [ + { + "enabled": False, + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "below": 110, + }, + ], + } + config = cv.CONDITION_SCHEMA(config) + config = await condition.async_validate_condition_config(hass, config) + test = await condition.async_from_config(hass, config) + + hass.states.async_set("sensor.temperature", 120) + assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": None}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "wanted_state_below": 110.0, + "state": 120.0, + } + } + ], + } + ) + + hass.states.async_set("sensor.temperature", 105) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": None}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 105.0}}], + } + ) + + hass.states.async_set("sensor.temperature", 100) + assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": None}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 100.0}}], + } + ) + + +async def test_or_condition_with_disabled_condition(hass): + """Test the 'or' condition with one of the conditions disabled.""" + config = { + "alias": "Or Condition", + "condition": "or", + "conditions": [ + { + "enabled": False, + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "below": 110, + }, + ], + } + config = cv.CONDITION_SCHEMA(config) + config = await condition.async_validate_condition_config(hass, config) + test = await condition.async_from_config(hass, config) + + hass.states.async_set("sensor.temperature", 120) + assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": None}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 120.0, + "wanted_state_below": 110.0, + } + } + ], + } + ) + + hass.states.async_set("sensor.temperature", 105) + assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": None}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 105.0}}], + } + ) + + hass.states.async_set("sensor.temperature", 100) + assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": None}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 100.0}}], + } + )