Allow referencing sensor entities for before/after in time conditions (#51444)
* Allow referencing sensor entities for before/after in time conditions * Fix typo in variable naming * Improve test coveragepull/51664/head
parent
88386a7f44
commit
f35929ba63
|
@ -16,7 +16,9 @@ from homeassistant.components import zone as zone_cmp
|
|||
from homeassistant.components.device_automation import (
|
||||
async_get_device_automation_platform,
|
||||
)
|
||||
from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_GPS_ACCURACY,
|
||||
ATTR_LATITUDE,
|
||||
ATTR_LONGITUDE,
|
||||
|
@ -736,11 +738,24 @@ def time(
|
|||
after_entity = hass.states.get(after)
|
||||
if not after_entity:
|
||||
raise ConditionErrorMessage("time", f"unknown 'after' entity {after}")
|
||||
after = dt_util.dt.time(
|
||||
after_entity.attributes.get("hour", 23),
|
||||
after_entity.attributes.get("minute", 59),
|
||||
after_entity.attributes.get("second", 59),
|
||||
)
|
||||
if after_entity.domain == "input_datetime":
|
||||
after = dt_util.dt.time(
|
||||
after_entity.attributes.get("hour", 23),
|
||||
after_entity.attributes.get("minute", 59),
|
||||
after_entity.attributes.get("second", 59),
|
||||
)
|
||||
elif after_entity.attributes.get(
|
||||
ATTR_DEVICE_CLASS
|
||||
) == DEVICE_CLASS_TIMESTAMP and after_entity.state not in (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
after_datetime = dt_util.parse_datetime(after_entity.state)
|
||||
if after_datetime is None:
|
||||
return False
|
||||
after = dt_util.as_local(after_datetime).time()
|
||||
else:
|
||||
return False
|
||||
|
||||
if before is None:
|
||||
before = dt_util.dt.time(23, 59, 59, 999999)
|
||||
|
@ -748,11 +763,24 @@ def time(
|
|||
before_entity = hass.states.get(before)
|
||||
if not before_entity:
|
||||
raise ConditionErrorMessage("time", f"unknown 'before' entity {before}")
|
||||
before = dt_util.dt.time(
|
||||
before_entity.attributes.get("hour", 23),
|
||||
before_entity.attributes.get("minute", 59),
|
||||
before_entity.attributes.get("second", 59),
|
||||
)
|
||||
if before_entity.domain == "input_datetime":
|
||||
before = dt_util.dt.time(
|
||||
before_entity.attributes.get("hour", 23),
|
||||
before_entity.attributes.get("minute", 59),
|
||||
before_entity.attributes.get("second", 59),
|
||||
)
|
||||
elif before_entity.attributes.get(
|
||||
ATTR_DEVICE_CLASS
|
||||
) == DEVICE_CLASS_TIMESTAMP and before_entity.state not in (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
before_timedatime = dt_util.parse_datetime(before_entity.state)
|
||||
if before_timedatime is None:
|
||||
return False
|
||||
before = dt_util.as_local(before_timedatime).time()
|
||||
else:
|
||||
return False
|
||||
|
||||
if after < before:
|
||||
condition_trace_update_result(after=after, now_time=now_time, before=before)
|
||||
|
|
|
@ -1014,8 +1014,12 @@ TIME_CONDITION_SCHEMA = vol.All(
|
|||
{
|
||||
**CONDITION_BASE_SCHEMA,
|
||||
vol.Required(CONF_CONDITION): "time",
|
||||
"before": vol.Any(time, vol.All(str, entity_domain("input_datetime"))),
|
||||
"after": vol.Any(time, vol.All(str, entity_domain("input_datetime"))),
|
||||
"before": vol.Any(
|
||||
time, vol.All(str, entity_domain(["input_datetime", "sensor"]))
|
||||
),
|
||||
"after": vol.Any(
|
||||
time, vol.All(str, entity_domain(["input_datetime", "sensor"]))
|
||||
),
|
||||
"weekday": weekdays,
|
||||
}
|
||||
),
|
||||
|
|
|
@ -6,7 +6,8 @@ import pytest
|
|||
|
||||
from homeassistant.components import sun
|
||||
import homeassistant.components.automation as automation
|
||||
from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET
|
||||
from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP
|
||||
from homeassistant.const import ATTR_DEVICE_CLASS, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET
|
||||
from homeassistant.exceptions import ConditionError, HomeAssistantError
|
||||
from homeassistant.helpers import condition, trace
|
||||
from homeassistant.helpers.template import Template
|
||||
|
@ -826,6 +827,90 @@ async def test_time_using_input_datetime(hass):
|
|||
condition.time(hass, before="input_datetime.not_existing")
|
||||
|
||||
|
||||
async def test_time_using_sensor(hass):
|
||||
"""Test time conditions using sensor entities."""
|
||||
hass.states.async_set(
|
||||
"sensor.am",
|
||||
"2021-06-03 13:00:00.000000+00:00", # 6 am local time
|
||||
{ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"sensor.pm",
|
||||
"2020-06-01 01:00:00.000000+00:00", # 6 pm local time
|
||||
{ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"sensor.no_device_class",
|
||||
"2020-06-01 01:00:00.000000+00:00",
|
||||
)
|
||||
hass.states.async_set(
|
||||
"sensor.invalid_timestamp",
|
||||
"This is not a timestamp",
|
||||
{ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP},
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.condition.dt_util.now",
|
||||
return_value=dt_util.now().replace(hour=3),
|
||||
):
|
||||
assert not condition.time(hass, after="sensor.am", before="sensor.pm")
|
||||
assert condition.time(hass, after="sensor.pm", before="sensor.am")
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.condition.dt_util.now",
|
||||
return_value=dt_util.now().replace(hour=9),
|
||||
):
|
||||
assert condition.time(hass, after="sensor.am", before="sensor.pm")
|
||||
assert not condition.time(hass, after="sensor.pm", before="sensor.am")
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.condition.dt_util.now",
|
||||
return_value=dt_util.now().replace(hour=15),
|
||||
):
|
||||
assert condition.time(hass, after="sensor.am", before="sensor.pm")
|
||||
assert not condition.time(hass, after="sensor.pm", before="sensor.am")
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.condition.dt_util.now",
|
||||
return_value=dt_util.now().replace(hour=21),
|
||||
):
|
||||
assert not condition.time(hass, after="sensor.am", before="sensor.pm")
|
||||
assert condition.time(hass, after="sensor.pm", before="sensor.am")
|
||||
|
||||
# Trigger on PM time
|
||||
with patch(
|
||||
"homeassistant.helpers.condition.dt_util.now",
|
||||
return_value=dt_util.now().replace(hour=18, minute=0, second=0),
|
||||
):
|
||||
assert condition.time(hass, after="sensor.pm", before="sensor.am")
|
||||
assert not condition.time(hass, after="sensor.am", before="sensor.pm")
|
||||
assert condition.time(hass, after="sensor.pm")
|
||||
assert not condition.time(hass, before="sensor.pm")
|
||||
|
||||
# Even though valid, the device class is missing
|
||||
assert not condition.time(hass, after="sensor.no_device_class")
|
||||
assert not condition.time(hass, before="sensor.no_device_class")
|
||||
|
||||
# Trigger on AM time
|
||||
with patch(
|
||||
"homeassistant.helpers.condition.dt_util.now",
|
||||
return_value=dt_util.now().replace(hour=6, minute=0, second=0),
|
||||
):
|
||||
assert not condition.time(hass, after="sensor.pm", before="sensor.am")
|
||||
assert condition.time(hass, after="sensor.am", before="sensor.pm")
|
||||
assert condition.time(hass, after="sensor.am")
|
||||
assert not condition.time(hass, before="sensor.am")
|
||||
|
||||
assert not condition.time(hass, after="sensor.invalid_timestamp")
|
||||
assert not condition.time(hass, before="sensor.invalid_timestamp")
|
||||
|
||||
with pytest.raises(ConditionError):
|
||||
condition.time(hass, after="sensor.not_existing")
|
||||
|
||||
with pytest.raises(ConditionError):
|
||||
condition.time(hass, before="sensor.not_existing")
|
||||
|
||||
|
||||
async def test_state_raises(hass):
|
||||
"""Test that state raises ConditionError on errors."""
|
||||
# No entity
|
||||
|
|
Loading…
Reference in New Issue