Add support for attributes in (numeric) state conditions (#39050)

pull/39057/head
Franck Nijhof 2020-08-19 20:01:27 +02:00 committed by GitHub
parent 5f10c55303
commit bdc5af8dd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 8 deletions

View File

@ -37,9 +37,10 @@ CONF_API_KEY = "api_key"
CONF_API_VERSION = "api_version"
CONF_ARMING_TIME = "arming_time"
CONF_AT = "at"
CONF_AUTHENTICATION = "authentication"
CONF_ATTRIBUTE = "attribute"
CONF_AUTH_MFA_MODULES = "auth_mfa_modules"
CONF_AUTH_PROVIDERS = "auth_providers"
CONF_AUTHENTICATION = "authentication"
CONF_BASE = "base"
CONF_BEFORE = "before"
CONF_BELOW = "below"

View File

@ -5,7 +5,7 @@ from datetime import datetime, timedelta
import functools as ft
import logging
import sys
from typing import Callable, Container, List, Optional, Set, Union, cast
from typing import Any, Callable, Container, List, Optional, Set, Union, cast
from homeassistant.components import zone as zone_cmp
from homeassistant.components.device_automation import (
@ -17,6 +17,7 @@ from homeassistant.const import (
ATTR_LONGITUDE,
CONF_ABOVE,
CONF_AFTER,
CONF_ATTRIBUTE,
CONF_BEFORE,
CONF_BELOW,
CONF_CONDITION,
@ -191,16 +192,21 @@ def async_numeric_state(
above: Optional[float] = None,
value_template: Optional[Template] = None,
variables: TemplateVarsType = None,
attribute: Optional[str] = None,
) -> bool:
"""Test a numeric state condition."""
if isinstance(entity, str):
entity = hass.states.get(entity)
if entity is None:
if entity is None or (attribute is not None and attribute not in entity.attributes):
return False
value: Any = None
if value_template is None:
value = entity.state
if attribute is None:
value = entity.state
else:
value = entity.attributes.get(attribute)
else:
variables = dict(variables or {})
variables["state"] = entity
@ -239,6 +245,7 @@ def async_numeric_state_from_config(
if config_validation:
config = cv.NUMERIC_STATE_CONDITION_SCHEMA(config)
entity_ids = config.get(CONF_ENTITY_ID, [])
attribute = config.get(CONF_ATTRIBUTE)
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
value_template = config.get(CONF_VALUE_TEMPLATE)
@ -252,7 +259,7 @@ def async_numeric_state_from_config(
return all(
async_numeric_state(
hass, entity_id, below, above, value_template, variables
hass, entity_id, below, above, value_template, variables, attribute
)
for entity_id in entity_ids
)
@ -265,6 +272,7 @@ def state(
entity: Union[None, str, State],
req_state: Union[str, List[str]],
for_period: Optional[timedelta] = None,
attribute: Optional[str] = None,
) -> bool:
"""Test if state matches requirements.
@ -273,14 +281,18 @@ def state(
if isinstance(entity, str):
entity = hass.states.get(entity)
if entity is None:
if entity is None or (attribute is not None and attribute not in entity.attributes):
return False
assert isinstance(entity, State)
if isinstance(req_state, str):
req_state = [req_state]
is_state = entity.state in req_state
if attribute is None:
is_state = entity.state in req_state
else:
is_state = str(entity.attributes.get(attribute)) in req_state
if for_period is None or not is_state:
return is_state
@ -297,6 +309,7 @@ def state_from_config(
entity_ids = config.get(CONF_ENTITY_ID, [])
req_states: Union[str, List[str]] = config.get(CONF_STATE, [])
for_period = config.get("for")
attribute = config.get(CONF_ATTRIBUTE)
if not isinstance(req_states, list):
req_states = [req_states]
@ -304,7 +317,8 @@ def state_from_config(
def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
"""Test if condition."""
return all(
state(hass, entity_id, req_states, for_period) for entity_id in entity_ids
state(hass, entity_id, req_states, for_period, attribute)
for entity_id in entity_ids
)
return if_state

View File

@ -37,6 +37,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_ABOVE,
CONF_ALIAS,
CONF_ATTRIBUTE,
CONF_BELOW,
CONF_CHOOSE,
CONF_CONDITION,
@ -868,6 +869,7 @@ NUMERIC_STATE_CONDITION_SCHEMA = vol.All(
{
vol.Required(CONF_CONDITION): "numeric_state",
vol.Required(CONF_ENTITY_ID): entity_ids,
vol.Optional(CONF_ATTRIBUTE): str,
CONF_BELOW: vol.Coerce(float),
CONF_ABOVE: vol.Coerce(float),
vol.Optional(CONF_VALUE_TEMPLATE): template,
@ -881,6 +883,7 @@ STATE_CONDITION_SCHEMA = vol.All(
{
vol.Required(CONF_CONDITION): "state",
vol.Required(CONF_ENTITY_ID): entity_ids,
vol.Optional(CONF_ATTRIBUTE): str,
vol.Required(CONF_STATE): vol.Any(str, [str]),
vol.Optional(CONF_FOR): positive_time_period,
# To support use_trigger_value in automation

View File

@ -323,6 +323,39 @@ async def test_multiple_states(hass):
assert not test(hass)
async def test_state_attribute(hass):
"""Test with state attribute in condition."""
test = await condition.async_from_config(
hass,
{
"condition": "and",
"conditions": [
{
"condition": "state",
"entity_id": "sensor.temperature",
"attribute": "attribute1",
"state": "200",
},
],
},
)
hass.states.async_set("sensor.temperature", 100, {"unkown_attr": 200})
assert not test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": 200})
assert test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": "200"})
assert test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": 201})
assert not test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": None})
assert not test(hass)
async def test_numeric_state_multiple_entities(hass):
"""Test with multiple entities in condition."""
test = await condition.async_from_config(
@ -352,6 +385,39 @@ async def test_numeric_state_multiple_entities(hass):
assert not test(hass)
async def test_numberic_state_attribute(hass):
"""Test with numeric state attribute in condition."""
test = await condition.async_from_config(
hass,
{
"condition": "and",
"conditions": [
{
"condition": "numeric_state",
"entity_id": "sensor.temperature",
"attribute": "attribute1",
"below": 50,
},
],
},
)
hass.states.async_set("sensor.temperature", 100, {"unkown_attr": 10})
assert not test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": 49})
assert test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": "49"})
assert test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": 51})
assert not test(hass)
hass.states.async_set("sensor.temperature", 100, {"attribute1": None})
assert not test(hass)
async def test_zone_multiple_entities(hass):
"""Test with multiple entities in condition."""
test = await condition.async_from_config(