2016-04-28 10:03:57 +00:00
|
|
|
"""Offer reusable conditions."""
|
2019-09-05 14:49:32 +00:00
|
|
|
import asyncio
|
2019-01-20 23:03:12 +00:00
|
|
|
from datetime import datetime, timedelta
|
2016-10-01 08:22:13 +00:00
|
|
|
import functools as ft
|
2016-04-28 10:03:57 +00:00
|
|
|
import logging
|
|
|
|
import sys
|
2019-01-20 23:03:12 +00:00
|
|
|
from typing import Callable, Container, Optional, Union, cast
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2017-05-09 07:03:34 +00:00
|
|
|
from homeassistant.components import zone as zone_cmp
|
2019-10-02 22:58:14 +00:00
|
|
|
from homeassistant.components.device_automation import (
|
|
|
|
async_get_device_automation_platform,
|
|
|
|
)
|
2016-04-28 10:03:57 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_GPS_ACCURACY,
|
|
|
|
ATTR_LATITUDE,
|
|
|
|
ATTR_LONGITUDE,
|
2019-10-02 22:58:14 +00:00
|
|
|
CONF_ABOVE,
|
|
|
|
CONF_AFTER,
|
|
|
|
CONF_BEFORE,
|
|
|
|
CONF_BELOW,
|
|
|
|
CONF_CONDITION,
|
2019-09-24 21:57:05 +00:00
|
|
|
CONF_DOMAIN,
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_ENTITY_ID,
|
|
|
|
CONF_STATE,
|
2019-10-02 22:58:14 +00:00
|
|
|
CONF_VALUE_TEMPLATE,
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_WEEKDAY,
|
2019-10-02 22:58:14 +00:00
|
|
|
CONF_ZONE,
|
2019-07-31 19:25:30 +00:00
|
|
|
STATE_UNAVAILABLE,
|
|
|
|
STATE_UNKNOWN,
|
2019-10-02 22:58:14 +00:00
|
|
|
SUN_EVENT_SUNRISE,
|
|
|
|
SUN_EVENT_SUNSET,
|
|
|
|
WEEKDAYS,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-12-09 15:42:10 +00:00
|
|
|
from homeassistant.core import HomeAssistant, State
|
|
|
|
from homeassistant.exceptions import HomeAssistantError, TemplateError
|
2016-04-28 10:03:57 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2017-05-09 07:03:34 +00:00
|
|
|
from homeassistant.helpers.sun import get_astral_event_date
|
2019-12-09 15:42:10 +00:00
|
|
|
from homeassistant.helpers.template import Template
|
|
|
|
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
2018-03-11 17:01:12 +00:00
|
|
|
from homeassistant.util.async_ import run_callback_threadsafe
|
2019-12-09 15:42:10 +00:00
|
|
|
import homeassistant.util.dt as dt_util
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
FROM_CONFIG_FORMAT = "{}_from_config"
|
|
|
|
ASYNC_FROM_CONFIG_FORMAT = "async_{}_from_config"
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-09-24 21:57:05 +00:00
|
|
|
ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool]
|
|
|
|
|
2016-10-01 06:11:57 +00:00
|
|
|
|
2019-09-05 14:49:32 +00:00
|
|
|
async def async_from_config(
|
|
|
|
hass: HomeAssistant, config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-10-01 06:11:57 +00:00
|
|
|
"""Turn a condition configuration into a method.
|
|
|
|
|
|
|
|
Should be run on the event loop.
|
|
|
|
"""
|
|
|
|
for fmt in (ASYNC_FROM_CONFIG_FORMAT, FROM_CONFIG_FORMAT):
|
|
|
|
factory = getattr(
|
2019-07-31 19:25:30 +00:00
|
|
|
sys.modules[__name__], fmt.format(config.get(CONF_CONDITION)), None
|
|
|
|
)
|
2016-10-01 06:11:57 +00:00
|
|
|
|
|
|
|
if factory:
|
|
|
|
break
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
if factory is None:
|
2019-07-31 19:25:30 +00:00
|
|
|
raise HomeAssistantError(
|
|
|
|
'Invalid condition "{}" specified {}'.format(
|
|
|
|
config.get(CONF_CONDITION), config
|
|
|
|
)
|
|
|
|
)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-09-05 14:49:32 +00:00
|
|
|
# Check for partials to properly determine if coroutine function
|
|
|
|
check_factory = factory
|
|
|
|
while isinstance(check_factory, ft.partial):
|
|
|
|
check_factory = check_factory.func
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-09-05 14:49:32 +00:00
|
|
|
if asyncio.iscoroutinefunction(check_factory):
|
2019-09-24 21:57:05 +00:00
|
|
|
return cast(
|
|
|
|
ConditionCheckerType, await factory(hass, config, config_validation)
|
|
|
|
)
|
|
|
|
return cast(ConditionCheckerType, factory(config, config_validation))
|
2016-10-01 06:11:57 +00:00
|
|
|
|
|
|
|
|
2019-09-05 14:49:32 +00:00
|
|
|
async def async_and_from_config(
|
|
|
|
hass: HomeAssistant, config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Create multi condition matcher using 'AND'."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.AND_CONDITION_SCHEMA(config)
|
2019-09-05 14:49:32 +00:00
|
|
|
checks = [
|
|
|
|
await async_from_config(hass, entry, False) for entry in config["conditions"]
|
|
|
|
]
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def if_and_condition(
|
|
|
|
hass: HomeAssistant, variables: TemplateVarsType = None
|
|
|
|
) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test and condition."""
|
2016-10-01 06:11:57 +00:00
|
|
|
try:
|
|
|
|
for check in checks:
|
2016-05-03 05:05:09 +00:00
|
|
|
if not check(hass, variables):
|
|
|
|
return False
|
2016-10-01 06:11:57 +00:00
|
|
|
except Exception as ex: # pylint: disable=broad-except
|
2017-05-02 16:18:47 +00:00
|
|
|
_LOGGER.warning("Error during and-condition: %s", ex)
|
2016-10-01 06:11:57 +00:00
|
|
|
return False
|
2016-05-03 05:05:09 +00:00
|
|
|
|
|
|
|
return True
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
return if_and_condition
|
|
|
|
|
|
|
|
|
2019-09-05 14:49:32 +00:00
|
|
|
async def async_or_from_config(
|
|
|
|
hass: HomeAssistant, config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Create multi condition matcher using 'OR'."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.OR_CONDITION_SCHEMA(config)
|
2019-09-05 14:49:32 +00:00
|
|
|
checks = [
|
|
|
|
await async_from_config(hass, entry, False) for entry in config["conditions"]
|
|
|
|
]
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def if_or_condition(
|
|
|
|
hass: HomeAssistant, variables: TemplateVarsType = None
|
|
|
|
) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test and condition."""
|
2016-10-01 06:11:57 +00:00
|
|
|
try:
|
|
|
|
for check in checks:
|
2016-05-03 05:05:09 +00:00
|
|
|
if check(hass, variables):
|
|
|
|
return True
|
2016-10-01 06:11:57 +00:00
|
|
|
except Exception as ex: # pylint: disable=broad-except
|
2017-05-02 16:18:47 +00:00
|
|
|
_LOGGER.warning("Error during or-condition: %s", ex)
|
2016-05-03 05:05:09 +00:00
|
|
|
|
|
|
|
return False
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
return if_or_condition
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def numeric_state(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entity: Union[None, str, State],
|
|
|
|
below: Optional[float] = None,
|
|
|
|
above: Optional[float] = None,
|
|
|
|
value_template: Optional[Template] = None,
|
|
|
|
variables: TemplateVarsType = None,
|
|
|
|
) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test a numeric state condition."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return cast(
|
|
|
|
bool,
|
|
|
|
run_callback_threadsafe(
|
|
|
|
hass.loop,
|
|
|
|
async_numeric_state,
|
|
|
|
hass,
|
|
|
|
entity,
|
|
|
|
below,
|
|
|
|
above,
|
|
|
|
value_template,
|
|
|
|
variables,
|
|
|
|
).result(),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def async_numeric_state(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entity: Union[None, str, State],
|
|
|
|
below: Optional[float] = None,
|
|
|
|
above: Optional[float] = None,
|
|
|
|
value_template: Optional[Template] = None,
|
|
|
|
variables: TemplateVarsType = None,
|
|
|
|
) -> bool:
|
2016-09-30 19:57:24 +00:00
|
|
|
"""Test a numeric state condition."""
|
2016-04-28 10:03:57 +00:00
|
|
|
if isinstance(entity, str):
|
|
|
|
entity = hass.states.get(entity)
|
|
|
|
|
|
|
|
if entity is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if value_template is None:
|
|
|
|
value = entity.state
|
|
|
|
else:
|
|
|
|
variables = dict(variables or {})
|
2019-07-31 19:25:30 +00:00
|
|
|
variables["state"] = entity
|
2016-04-28 10:03:57 +00:00
|
|
|
try:
|
2016-09-30 19:57:24 +00:00
|
|
|
value = value_template.async_render(variables)
|
2016-04-28 10:03:57 +00:00
|
|
|
except TemplateError as ex:
|
2016-08-07 23:26:35 +00:00
|
|
|
_LOGGER.error("Template error: %s", ex)
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
2018-02-20 16:02:27 +00:00
|
|
|
if value in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
|
|
|
return False
|
|
|
|
|
2016-04-28 10:03:57 +00:00
|
|
|
try:
|
2019-01-20 23:03:12 +00:00
|
|
|
fvalue = float(value)
|
2016-04-28 10:03:57 +00:00
|
|
|
except ValueError:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.warning(
|
2020-01-02 19:17:10 +00:00
|
|
|
"Value cannot be processed as a number: %s (Offending entity: %s)",
|
2019-07-31 19:25:30 +00:00
|
|
|
entity,
|
|
|
|
value,
|
|
|
|
)
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
2019-01-20 23:03:12 +00:00
|
|
|
if below is not None and fvalue >= below:
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
2019-01-20 23:03:12 +00:00
|
|
|
if above is not None and fvalue <= above:
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def async_numeric_state_from_config(
|
|
|
|
config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Wrap action method with state based condition."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.NUMERIC_STATE_CONDITION_SCHEMA(config)
|
|
|
|
entity_id = config.get(CONF_ENTITY_ID)
|
|
|
|
below = config.get(CONF_BELOW)
|
|
|
|
above = config.get(CONF_ABOVE)
|
|
|
|
value_template = config.get(CONF_VALUE_TEMPLATE)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def if_numeric_state(
|
|
|
|
hass: HomeAssistant, variables: TemplateVarsType = None
|
|
|
|
) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test numeric state condition."""
|
2016-09-28 04:29:55 +00:00
|
|
|
if value_template is not None:
|
|
|
|
value_template.hass = hass
|
|
|
|
|
2016-10-01 06:11:57 +00:00
|
|
|
return async_numeric_state(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass, entity_id, below, above, value_template, variables
|
|
|
|
)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
return if_numeric_state
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def state(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entity: Union[None, str, State],
|
|
|
|
req_state: str,
|
|
|
|
for_period: Optional[timedelta] = None,
|
|
|
|
) -> bool:
|
2017-02-02 05:44:05 +00:00
|
|
|
"""Test if state matches requirements.
|
|
|
|
|
|
|
|
Async friendly.
|
|
|
|
"""
|
2016-04-28 10:03:57 +00:00
|
|
|
if isinstance(entity, str):
|
|
|
|
entity = hass.states.get(entity)
|
|
|
|
|
|
|
|
if entity is None:
|
|
|
|
return False
|
2019-01-20 23:03:12 +00:00
|
|
|
assert isinstance(entity, State)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
is_state = entity.state == req_state
|
|
|
|
|
|
|
|
if for_period is None or not is_state:
|
|
|
|
return is_state
|
|
|
|
|
|
|
|
return dt_util.utcnow() - for_period > entity.last_changed
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def state_from_config(
|
|
|
|
config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Wrap action method with state based condition."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.STATE_CONDITION_SCHEMA(config)
|
|
|
|
entity_id = config.get(CONF_ENTITY_ID)
|
2019-01-20 23:03:12 +00:00
|
|
|
req_state = cast(str, config.get(CONF_STATE))
|
2019-07-31 19:25:30 +00:00
|
|
|
for_period = config.get("for")
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test if condition."""
|
|
|
|
return state(hass, entity_id, req_state, for_period)
|
|
|
|
|
|
|
|
return if_state
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def sun(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
before: Optional[str] = None,
|
|
|
|
after: Optional[str] = None,
|
|
|
|
before_offset: Optional[timedelta] = None,
|
|
|
|
after_offset: Optional[timedelta] = None,
|
|
|
|
) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test if current time matches sun requirements."""
|
2017-05-09 07:03:34 +00:00
|
|
|
utcnow = dt_util.utcnow()
|
|
|
|
today = dt_util.as_local(utcnow).date()
|
2016-04-28 10:03:57 +00:00
|
|
|
before_offset = before_offset or timedelta(0)
|
|
|
|
after_offset = after_offset or timedelta(0)
|
|
|
|
|
2019-08-16 15:34:56 +00:00
|
|
|
sunrise_today = get_astral_event_date(hass, SUN_EVENT_SUNRISE, today)
|
|
|
|
sunset_today = get_astral_event_date(hass, SUN_EVENT_SUNSET, today)
|
|
|
|
|
|
|
|
sunrise = sunrise_today
|
|
|
|
sunset = sunset_today
|
|
|
|
if today > dt_util.as_local(
|
|
|
|
cast(datetime, sunrise_today)
|
|
|
|
).date() and SUN_EVENT_SUNRISE in (before, after):
|
|
|
|
tomorrow = dt_util.as_local(utcnow + timedelta(days=1)).date()
|
|
|
|
sunrise_tomorrow = get_astral_event_date(hass, SUN_EVENT_SUNRISE, tomorrow)
|
|
|
|
sunrise = sunrise_tomorrow
|
|
|
|
|
|
|
|
if today > dt_util.as_local(
|
|
|
|
cast(datetime, sunset_today)
|
|
|
|
).date() and SUN_EVENT_SUNSET in (before, after):
|
|
|
|
tomorrow = dt_util.as_local(utcnow + timedelta(days=1)).date()
|
|
|
|
sunset_tomorrow = get_astral_event_date(hass, SUN_EVENT_SUNSET, tomorrow)
|
|
|
|
sunset = sunset_tomorrow
|
2017-05-09 07:03:34 +00:00
|
|
|
|
2018-07-17 17:34:29 +00:00
|
|
|
if sunrise is None and SUN_EVENT_SUNRISE in (before, after):
|
2017-05-09 07:03:34 +00:00
|
|
|
# There is no sunrise today
|
|
|
|
return False
|
|
|
|
|
2018-07-17 17:34:29 +00:00
|
|
|
if sunset is None and SUN_EVENT_SUNSET in (before, after):
|
2017-05-09 07:03:34 +00:00
|
|
|
# There is no sunset today
|
|
|
|
return False
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if before == SUN_EVENT_SUNRISE and utcnow > cast(datetime, sunrise) + before_offset:
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if before == SUN_EVENT_SUNSET and utcnow > cast(datetime, sunset) + before_offset:
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if after == SUN_EVENT_SUNRISE and utcnow < cast(datetime, sunrise) + after_offset:
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if after == SUN_EVENT_SUNSET and utcnow < cast(datetime, sunset) + after_offset:
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def sun_from_config(
|
|
|
|
config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Wrap action method with sun based condition."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.SUN_CONDITION_SCHEMA(config)
|
2019-07-31 19:25:30 +00:00
|
|
|
before = config.get("before")
|
|
|
|
after = config.get("after")
|
|
|
|
before_offset = config.get("before_offset")
|
|
|
|
after_offset = config.get("after_offset")
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def time_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Validate time based if-condition."""
|
|
|
|
return sun(hass, before, after, before_offset, after_offset)
|
|
|
|
|
|
|
|
return time_if
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def template(
|
|
|
|
hass: HomeAssistant, value_template: Template, variables: TemplateVarsType = None
|
|
|
|
) -> bool:
|
2016-09-28 04:29:55 +00:00
|
|
|
"""Test if template condition matches."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return cast(
|
|
|
|
bool,
|
|
|
|
run_callback_threadsafe(
|
|
|
|
hass.loop, async_template, hass, value_template, variables
|
|
|
|
).result(),
|
|
|
|
)
|
2016-09-28 04:29:55 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def async_template(
|
|
|
|
hass: HomeAssistant, value_template: Template, variables: TemplateVarsType = None
|
|
|
|
) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test if template condition matches."""
|
|
|
|
try:
|
2016-09-28 04:29:55 +00:00
|
|
|
value = value_template.async_render(variables)
|
2016-04-28 10:03:57 +00:00
|
|
|
except TemplateError as ex:
|
2017-05-02 16:18:47 +00:00
|
|
|
_LOGGER.error("Error during template condition: %s", ex)
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
return value.lower() == "true"
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def async_template_from_config(
|
|
|
|
config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Wrap action method with state based condition."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.TEMPLATE_CONDITION_SCHEMA(config)
|
2019-01-20 23:03:12 +00:00
|
|
|
value_template = cast(Template, config.get(CONF_VALUE_TEMPLATE))
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def template_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Validate template based if-condition."""
|
2016-09-28 04:29:55 +00:00
|
|
|
value_template.hass = hass
|
2016-09-25 20:33:01 +00:00
|
|
|
|
2016-10-01 06:11:57 +00:00
|
|
|
return async_template(hass, value_template, variables)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
return template_if
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def time(
|
|
|
|
before: Optional[dt_util.dt.time] = None,
|
|
|
|
after: Optional[dt_util.dt.time] = None,
|
|
|
|
weekday: Union[None, str, Container[str]] = None,
|
|
|
|
) -> bool:
|
2016-05-29 21:32:32 +00:00
|
|
|
"""Test if local time condition matches.
|
|
|
|
|
|
|
|
Handle the fact that time is continuous and we may be testing for
|
|
|
|
a period that crosses midnight. In that case it is easier to test
|
|
|
|
for the opposite. "(23:59 <= now < 00:01)" would be the same as
|
|
|
|
"not (00:01 <= now < 23:59)".
|
|
|
|
"""
|
2016-04-28 10:03:57 +00:00
|
|
|
now = dt_util.now()
|
|
|
|
now_time = now.time()
|
|
|
|
|
2016-05-29 21:32:32 +00:00
|
|
|
if after is None:
|
|
|
|
after = dt_util.dt.time(0)
|
|
|
|
if before is None:
|
|
|
|
before = dt_util.dt.time(23, 59, 59, 999999)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2016-05-29 21:32:32 +00:00
|
|
|
if after < before:
|
|
|
|
if not after <= now_time < before:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
if before <= now_time < after:
|
|
|
|
return False
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
if weekday is not None:
|
|
|
|
now_weekday = WEEKDAYS[now.weekday()]
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
isinstance(weekday, str)
|
|
|
|
and weekday != now_weekday
|
|
|
|
or now_weekday not in weekday
|
|
|
|
):
|
2016-04-28 10:03:57 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def time_from_config(
|
|
|
|
config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Wrap action method with time based condition."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.TIME_CONDITION_SCHEMA(config)
|
2016-05-03 05:05:09 +00:00
|
|
|
before = config.get(CONF_BEFORE)
|
|
|
|
after = config.get(CONF_AFTER)
|
|
|
|
weekday = config.get(CONF_WEEKDAY)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def time_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Validate time based if-condition."""
|
|
|
|
return time(before, after, weekday)
|
|
|
|
|
|
|
|
return time_if
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def zone(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
zone_ent: Union[None, str, State],
|
|
|
|
entity: Union[None, str, State],
|
|
|
|
) -> bool:
|
2016-09-30 19:57:24 +00:00
|
|
|
"""Test if zone-condition matches.
|
|
|
|
|
2017-02-02 05:44:05 +00:00
|
|
|
Async friendly.
|
2016-09-30 19:57:24 +00:00
|
|
|
"""
|
2016-04-28 10:03:57 +00:00
|
|
|
if isinstance(zone_ent, str):
|
|
|
|
zone_ent = hass.states.get(zone_ent)
|
|
|
|
|
|
|
|
if zone_ent is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if isinstance(entity, str):
|
|
|
|
entity = hass.states.get(entity)
|
|
|
|
|
|
|
|
if entity is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
latitude = entity.attributes.get(ATTR_LATITUDE)
|
|
|
|
longitude = entity.attributes.get(ATTR_LONGITUDE)
|
|
|
|
|
|
|
|
if latitude is None or longitude is None:
|
|
|
|
return False
|
|
|
|
|
2020-01-22 20:36:25 +00:00
|
|
|
return zone_cmp.in_zone(
|
2019-07-31 19:25:30 +00:00
|
|
|
zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0)
|
|
|
|
)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def zone_from_config(
|
|
|
|
config: ConfigType, config_validation: bool = True
|
2019-09-24 21:57:05 +00:00
|
|
|
) -> ConditionCheckerType:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Wrap action method with zone based condition."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.ZONE_CONDITION_SCHEMA(config)
|
|
|
|
entity_id = config.get(CONF_ENTITY_ID)
|
2016-05-03 05:05:09 +00:00
|
|
|
zone_entity_id = config.get(CONF_ZONE)
|
2016-04-28 10:03:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
2016-04-28 10:03:57 +00:00
|
|
|
"""Test if condition."""
|
|
|
|
return zone(hass, zone_entity_id, entity_id)
|
|
|
|
|
|
|
|
return if_in_zone
|
2019-09-24 21:57:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_device_from_config(
|
|
|
|
hass: HomeAssistant, config: ConfigType, config_validation: bool = True
|
|
|
|
) -> ConditionCheckerType:
|
|
|
|
"""Test a device condition."""
|
|
|
|
if config_validation:
|
|
|
|
config = cv.DEVICE_CONDITION_SCHEMA(config)
|
2019-10-02 22:58:14 +00:00
|
|
|
platform = await async_get_device_automation_platform(
|
|
|
|
hass, config[CONF_DOMAIN], "condition"
|
|
|
|
)
|
2019-09-24 21:57:05 +00:00
|
|
|
return cast(
|
|
|
|
ConditionCheckerType,
|
|
|
|
platform.async_condition_from_config(config, config_validation), # type: ignore
|
|
|
|
)
|
2019-10-02 22:58:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_validate_condition_config(
|
|
|
|
hass: HomeAssistant, config: ConfigType
|
|
|
|
) -> ConfigType:
|
|
|
|
"""Validate config."""
|
|
|
|
condition = config[CONF_CONDITION]
|
|
|
|
if condition in ("and", "or"):
|
|
|
|
conditions = []
|
|
|
|
for sub_cond in config["conditions"]:
|
|
|
|
sub_cond = await async_validate_condition_config(hass, sub_cond)
|
|
|
|
conditions.append(sub_cond)
|
|
|
|
config["conditions"] = conditions
|
|
|
|
|
|
|
|
if condition == "device":
|
|
|
|
config = cv.DEVICE_CONDITION_SCHEMA(config)
|
|
|
|
platform = await async_get_device_automation_platform(
|
|
|
|
hass, config[CONF_DOMAIN], "condition"
|
|
|
|
)
|
|
|
|
return cast(ConfigType, platform.CONDITION_SCHEMA(config)) # type: ignore
|
|
|
|
|
|
|
|
return config
|