Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/automation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

CONDITION_USE_TRIGGER_VALUES = "use_trigger_values"
CONDITION_TYPE_AND = "and"
CONDITION_TYPE_NOT = "not"
CONDITION_TYPE_OR = "or"

DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
Expand Down
26 changes: 26 additions & 0 deletions homeassistant/helpers/condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,32 @@ def if_or_condition(
return if_or_condition


async def async_not_from_config(
hass: HomeAssistant, config: ConfigType, config_validation: bool = True
) -> ConditionCheckerType:
"""Create multi condition matcher using 'NOT'."""
if config_validation:
config = cv.NOT_CONDITION_SCHEMA(config)
checks = [
await async_from_config(hass, entry, False) for entry in config["conditions"]
]

def if_not_condition(
hass: HomeAssistant, variables: TemplateVarsType = None
) -> bool:
"""Test not condition."""
try:
for check in checks:
if check(hass, variables):
return False
except Exception as ex: # pylint: disable=broad-except
_LOGGER.warning("Error during not-condition: %s", ex)

return True

return if_not_condition


def numeric_state(
hass: HomeAssistant,
entity: Union[None, str, State],
Expand Down
12 changes: 12 additions & 0 deletions homeassistant/helpers/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,17 @@ def make_entity_service_schema(
}
)

NOT_CONDITION_SCHEMA = vol.Schema(
{
vol.Required(CONF_CONDITION): "not",
vol.Required("conditions"): vol.All(
ensure_list,
# pylint: disable=unnecessary-lambda
[lambda value: CONDITION_SCHEMA(value)],
),
}
)

DEVICE_CONDITION_BASE_SCHEMA = vol.Schema(
{
vol.Required(CONF_CONDITION): "device",
Expand All @@ -945,6 +956,7 @@ def make_entity_service_schema(
"zone": ZONE_CONDITION_SCHEMA,
"and": AND_CONDITION_SCHEMA,
"or": OR_CONDITION_SCHEMA,
"not": NOT_CONDITION_SCHEMA,
"device": DEVICE_CONDITION_SCHEMA,
},
)
Expand Down
67 changes: 67 additions & 0 deletions tests/helpers/test_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,73 @@ async def test_or_condition_with_template(hass):
assert test(hass)


async def test_not_condition(hass):
"""Test the 'not' condition."""
test = await condition.async_from_config(
hass,
{
"condition": "not",
"conditions": [
{
"condition": "state",
"entity_id": "sensor.temperature",
"state": "100",
},
{
"condition": "numeric_state",
"entity_id": "sensor.temperature",
"below": 50,
},
],
},
)

hass.states.async_set("sensor.temperature", 101)
assert test(hass)

hass.states.async_set("sensor.temperature", 50)
assert test(hass)

hass.states.async_set("sensor.temperature", 49)
assert not test(hass)

hass.states.async_set("sensor.temperature", 100)
assert not test(hass)


async def test_not_condition_with_template(hass):
"""Test the 'or' condition."""
test = await condition.async_from_config(
hass,
{
"condition": "not",
"conditions": [
{
"condition": "template",
"value_template": '{{ states.sensor.temperature.state == "100" }}',
},
{
"condition": "numeric_state",
"entity_id": "sensor.temperature",
"below": 50,
},
],
},
)

hass.states.async_set("sensor.temperature", 101)
assert test(hass)

hass.states.async_set("sensor.temperature", 50)
assert test(hass)

hass.states.async_set("sensor.temperature", 49)
assert not test(hass)

hass.states.async_set("sensor.temperature", 100)
assert not test(hass)


async def test_time_window(hass):
"""Test time condition windows."""
sixam = dt.parse_time("06:00:00")
Expand Down