mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 22:57:17 +00:00
Initial script condition support (#1910)
This commit is contained in:
parent
953223b81b
commit
6354399d55
@ -11,7 +11,8 @@ import voluptuous as vol
|
|||||||
from homeassistant.bootstrap import prepare_setup_platform
|
from homeassistant.bootstrap import prepare_setup_platform
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
from homeassistant.components import logbook
|
from homeassistant.components import logbook
|
||||||
from homeassistant.helpers import extract_domain_configs, script
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import extract_domain_configs, script, condition
|
||||||
from homeassistant.loader import get_platform
|
from homeassistant.loader import get_platform
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -73,10 +74,11 @@ _CONDITION_SCHEMA = vol.Any(
|
|||||||
[
|
[
|
||||||
vol.All(
|
vol.All(
|
||||||
vol.Schema({
|
vol.Schema({
|
||||||
vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN),
|
CONF_PLATFORM: str,
|
||||||
|
CONF_CONDITION: str,
|
||||||
}, extra=vol.ALLOW_EXTRA),
|
}, extra=vol.ALLOW_EXTRA),
|
||||||
_platform_validator(METHOD_IF_ACTION, 'IF_ACTION_SCHEMA'),
|
cv.has_at_least_one_key(CONF_PLATFORM, CONF_CONDITION),
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -93,15 +95,17 @@ PLATFORM_SCHEMA = vol.Schema({
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Setup the automation."""
|
"""Setup the automation."""
|
||||||
|
success = False
|
||||||
for config_key in extract_domain_configs(config, DOMAIN):
|
for config_key in extract_domain_configs(config, DOMAIN):
|
||||||
conf = config[config_key]
|
conf = config[config_key]
|
||||||
|
|
||||||
for list_no, config_block in enumerate(conf):
|
for list_no, config_block in enumerate(conf):
|
||||||
name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
|
name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
|
||||||
list_no))
|
list_no))
|
||||||
_setup_automation(hass, config_block, name, config)
|
success = (_setup_automation(hass, config_block, name, config) or
|
||||||
|
success)
|
||||||
|
|
||||||
return True
|
return success
|
||||||
|
|
||||||
|
|
||||||
def _setup_automation(hass, config_block, name, config):
|
def _setup_automation(hass, config_block, name, config):
|
||||||
@ -136,7 +140,6 @@ def _process_if(hass, config, p_config, action):
|
|||||||
"""Process if checks."""
|
"""Process if checks."""
|
||||||
cond_type = p_config.get(CONF_CONDITION_TYPE,
|
cond_type = p_config.get(CONF_CONDITION_TYPE,
|
||||||
DEFAULT_CONDITION_TYPE).lower()
|
DEFAULT_CONDITION_TYPE).lower()
|
||||||
|
|
||||||
if_configs = p_config.get(CONF_CONDITION)
|
if_configs = p_config.get(CONF_CONDITION)
|
||||||
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
|
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
|
||||||
|
|
||||||
@ -145,28 +148,32 @@ def _process_if(hass, config, p_config, action):
|
|||||||
|
|
||||||
checks = []
|
checks = []
|
||||||
for if_config in if_configs:
|
for if_config in if_configs:
|
||||||
platform = _resolve_platform(METHOD_IF_ACTION, hass, config,
|
if CONF_PLATFORM in if_config:
|
||||||
if_config.get(CONF_PLATFORM))
|
if not use_trigger:
|
||||||
if platform is None:
|
_LOGGER.warning("Please switch your condition configuration "
|
||||||
continue
|
"to use 'condition' instead of 'platform'.")
|
||||||
|
if_config = dict(if_config)
|
||||||
check = platform.if_action(hass, if_config)
|
if_config[CONF_CONDITION] = if_config.pop(CONF_PLATFORM)
|
||||||
|
|
||||||
|
try:
|
||||||
|
checks.append(condition.from_config(if_config))
|
||||||
|
except HomeAssistantError as ex:
|
||||||
# Invalid conditions are allowed if we base it on trigger
|
# Invalid conditions are allowed if we base it on trigger
|
||||||
if check is None and not use_trigger:
|
if use_trigger:
|
||||||
|
_LOGGER.warning('Ignoring invalid condition: %s', ex)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning('Invalid condition: %s', ex)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
checks.append(check)
|
|
||||||
|
|
||||||
if cond_type == CONDITION_TYPE_AND:
|
if cond_type == CONDITION_TYPE_AND:
|
||||||
def if_action(variables=None):
|
def if_action(variables=None):
|
||||||
"""AND all conditions."""
|
"""AND all conditions."""
|
||||||
if all(check(variables) for check in checks):
|
if all(check(hass, variables) for check in checks):
|
||||||
action(variables)
|
action(variables)
|
||||||
else:
|
else:
|
||||||
def if_action(variables=None):
|
def if_action(variables=None):
|
||||||
"""OR all conditions."""
|
"""OR all conditions."""
|
||||||
if any(check(variables) for check in checks):
|
if any(check(hass, variables) for check in checks):
|
||||||
action(variables)
|
action(variables)
|
||||||
|
|
||||||
return if_action
|
return if_action
|
||||||
|
@ -6,20 +6,26 @@ at https://home-assistant.io/components/automation/#event-trigger
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
CONF_EVENT_TYPE = "event_type"
|
CONF_EVENT_TYPE = "event_type"
|
||||||
CONF_EVENT_DATA = "event_data"
|
CONF_EVENT_DATA = "event_data"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TRIGGER_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_PLATFORM): 'event',
|
||||||
|
vol.Required(CONF_EVENT_TYPE): cv.string,
|
||||||
|
vol.Optional(CONF_EVENT_DATA): dict,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
"""Listen for events based on configuration."""
|
"""Listen for events based on configuration."""
|
||||||
event_type = config.get(CONF_EVENT_TYPE)
|
event_type = config.get(CONF_EVENT_TYPE)
|
||||||
|
|
||||||
if event_type is None:
|
|
||||||
_LOGGER.error("Missing configuration key %s", CONF_EVENT_TYPE)
|
|
||||||
return False
|
|
||||||
|
|
||||||
event_data = config.get(CONF_EVENT_DATA)
|
event_data = config.get(CONF_EVENT_DATA)
|
||||||
|
|
||||||
def handle_event(event):
|
def handle_event(event):
|
||||||
|
@ -5,54 +5,36 @@ For more details about this automation rule, please refer to the documentation
|
|||||||
at https://home-assistant.io/components/automation/#numeric-state-trigger
|
at https://home-assistant.io/components/automation/#numeric-state-trigger
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from homeassistant.const import CONF_VALUE_TEMPLATE
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
|
||||||
|
CONF_BELOW, CONF_ABOVE)
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import condition, config_validation as cv
|
||||||
|
|
||||||
CONF_ENTITY_ID = "entity_id"
|
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||||
CONF_BELOW = "below"
|
vol.Required(CONF_PLATFORM): 'numeric_state',
|
||||||
CONF_ABOVE = "above"
|
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||||
|
CONF_BELOW: vol.Coerce(float),
|
||||||
|
CONF_ABOVE: vol.Coerce(float),
|
||||||
|
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||||
|
}), cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE))
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _renderer(hass, value_template, state, variables=None):
|
|
||||||
"""Render the state value."""
|
|
||||||
if value_template is None:
|
|
||||||
return state.state
|
|
||||||
|
|
||||||
variables = dict(variables or {})
|
|
||||||
variables['state'] = state
|
|
||||||
|
|
||||||
return template.render(hass, value_template, variables)
|
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
|
|
||||||
if entity_id is None:
|
|
||||||
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
|
|
||||||
return False
|
|
||||||
|
|
||||||
below = config.get(CONF_BELOW)
|
below = config.get(CONF_BELOW)
|
||||||
above = config.get(CONF_ABOVE)
|
above = config.get(CONF_ABOVE)
|
||||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
if below is None and above is None:
|
|
||||||
_LOGGER.error("Missing configuration key."
|
|
||||||
" One of %s or %s is required",
|
|
||||||
CONF_BELOW, CONF_ABOVE)
|
|
||||||
return False
|
|
||||||
|
|
||||||
renderer = partial(_renderer, hass, value_template)
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def state_automation_listener(entity, from_s, to_s):
|
def state_automation_listener(entity, from_s, to_s):
|
||||||
"""Listen for state changes and calls action."""
|
"""Listen for state changes and calls action."""
|
||||||
# Fire action if we go from outside range into range
|
|
||||||
if to_s is None:
|
if to_s is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -64,14 +46,20 @@ def trigger(hass, config, action):
|
|||||||
'above': above,
|
'above': above,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
to_s_value = renderer(to_s, variables)
|
|
||||||
from_s_value = None if from_s is None else renderer(from_s, variables)
|
# If new one doesn't match, nothing to do
|
||||||
if _in_range(above, below, to_s_value) and \
|
if not condition.numeric_state(
|
||||||
(from_s is None or not _in_range(above, below, from_s_value)):
|
hass, to_s, below, above, value_template, variables):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Only match if old didn't exist or existed but didn't match
|
||||||
|
# Written as: skip if old one did exist and matched
|
||||||
|
if from_s is not None and condition.numeric_state(
|
||||||
|
hass, from_s, below, above, value_template, variables):
|
||||||
|
return
|
||||||
|
|
||||||
variables['trigger']['from_state'] = from_s
|
variables['trigger']['from_state'] = from_s
|
||||||
variables['trigger']['from_value'] = from_s_value
|
|
||||||
variables['trigger']['to_state'] = to_s
|
variables['trigger']['to_state'] = to_s
|
||||||
variables['trigger']['to_value'] = to_s_value
|
|
||||||
|
|
||||||
action(variables)
|
action(variables)
|
||||||
|
|
||||||
@ -79,48 +67,3 @@ def trigger(hass, config, action):
|
|||||||
hass, entity_id, state_automation_listener)
|
hass, entity_id, state_automation_listener)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config):
|
|
||||||
"""Wrap action method with state based condition."""
|
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
|
||||||
|
|
||||||
if entity_id is None:
|
|
||||||
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
|
|
||||||
return None
|
|
||||||
|
|
||||||
below = config.get(CONF_BELOW)
|
|
||||||
above = config.get(CONF_ABOVE)
|
|
||||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
|
||||||
|
|
||||||
if below is None and above is None:
|
|
||||||
_LOGGER.error("Missing configuration key."
|
|
||||||
" One of %s or %s is required",
|
|
||||||
CONF_BELOW, CONF_ABOVE)
|
|
||||||
return None
|
|
||||||
|
|
||||||
renderer = partial(_renderer, hass, value_template)
|
|
||||||
|
|
||||||
def if_numeric_state(variables):
|
|
||||||
"""Test numeric state condition."""
|
|
||||||
state = hass.states.get(entity_id)
|
|
||||||
return state is not None and _in_range(above, below, renderer(state))
|
|
||||||
|
|
||||||
return if_numeric_state
|
|
||||||
|
|
||||||
|
|
||||||
def _in_range(range_start, range_end, value):
|
|
||||||
"""Check if value is inside the range."""
|
|
||||||
try:
|
|
||||||
value = float(value)
|
|
||||||
except ValueError:
|
|
||||||
_LOGGER.warning("Value returned from template is not a number: %s",
|
|
||||||
value)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if range_start is not None and range_end is not None:
|
|
||||||
return float(range_start) <= value < float(range_end)
|
|
||||||
elif range_end is not None:
|
|
||||||
return value < float(range_end)
|
|
||||||
else:
|
|
||||||
return float(range_start) <= value
|
|
||||||
|
@ -4,15 +4,11 @@ Offer state listening automation rules.
|
|||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#state-trigger
|
at https://home-assistant.io/components/automation/#state-trigger
|
||||||
"""
|
"""
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, CONF_PLATFORM)
|
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, CONF_PLATFORM)
|
||||||
from homeassistant.components.automation.time import (
|
|
||||||
CONF_HOURS, CONF_MINUTES, CONF_SECONDS)
|
|
||||||
from homeassistant.helpers.event import track_state_change, track_point_in_time
|
from homeassistant.helpers.event import track_state_change, track_point_in_time
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -22,46 +18,19 @@ CONF_TO = "to"
|
|||||||
CONF_STATE = "state"
|
CONF_STATE = "state"
|
||||||
CONF_FOR = "for"
|
CONF_FOR = "for"
|
||||||
|
|
||||||
BASE_SCHEMA = vol.Schema({
|
TRIGGER_SCHEMA = vol.All(
|
||||||
|
vol.Schema({
|
||||||
vol.Required(CONF_PLATFORM): 'state',
|
vol.Required(CONF_PLATFORM): 'state',
|
||||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||||
# These are str on purpose. Want to catch YAML conversions
|
|
||||||
CONF_STATE: str,
|
|
||||||
CONF_FOR: vol.All(vol.Schema({
|
|
||||||
CONF_HOURS: vol.Coerce(int),
|
|
||||||
CONF_MINUTES: vol.Coerce(int),
|
|
||||||
CONF_SECONDS: vol.Coerce(int),
|
|
||||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS)),
|
|
||||||
})
|
|
||||||
|
|
||||||
TRIGGER_SCHEMA = vol.Schema(vol.All(
|
|
||||||
BASE_SCHEMA.extend({
|
|
||||||
# These are str on purpose. Want to catch YAML conversions
|
# These are str on purpose. Want to catch YAML conversions
|
||||||
CONF_FROM: str,
|
CONF_FROM: str,
|
||||||
CONF_TO: str,
|
CONF_TO: str,
|
||||||
|
CONF_STATE: str,
|
||||||
|
CONF_FOR: vol.All(cv.time_period, cv.positive_timedelta),
|
||||||
}),
|
}),
|
||||||
vol.Any(cv.key_dependency(CONF_FOR, CONF_TO),
|
vol.Any(cv.key_dependency(CONF_FOR, CONF_TO),
|
||||||
cv.key_dependency(CONF_FOR, CONF_STATE))
|
cv.key_dependency(CONF_FOR, CONF_STATE))
|
||||||
))
|
)
|
||||||
|
|
||||||
IF_ACTION_SCHEMA = vol.Schema(vol.All(
|
|
||||||
BASE_SCHEMA,
|
|
||||||
cv.key_dependency(CONF_FOR, CONF_STATE)
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def get_time_config(config):
|
|
||||||
"""Helper function to extract the time specified in the configuration."""
|
|
||||||
if CONF_FOR not in config:
|
|
||||||
return None
|
|
||||||
|
|
||||||
hours = config[CONF_FOR].get(CONF_HOURS)
|
|
||||||
minutes = config[CONF_FOR].get(CONF_MINUTES)
|
|
||||||
seconds = config[CONF_FOR].get(CONF_SECONDS)
|
|
||||||
|
|
||||||
return timedelta(hours=(hours or 0.0),
|
|
||||||
minutes=(minutes or 0.0),
|
|
||||||
seconds=(seconds or 0.0))
|
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
@ -69,7 +38,7 @@ def trigger(hass, config, action):
|
|||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
from_state = config.get(CONF_FROM, MATCH_ALL)
|
from_state = config.get(CONF_FROM, MATCH_ALL)
|
||||||
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
|
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
|
||||||
time_delta = get_time_config(config)
|
time_delta = config.get(CONF_FOR)
|
||||||
|
|
||||||
def state_automation_listener(entity, from_s, to_s):
|
def state_automation_listener(entity, from_s, to_s):
|
||||||
"""Listen for state changes and calls action."""
|
"""Listen for state changes and calls action."""
|
||||||
@ -97,7 +66,7 @@ def trigger(hass, config, action):
|
|||||||
|
|
||||||
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
|
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
|
||||||
"""Fire on changes and cancel for listener if changed."""
|
"""Fire on changes and cancel for listener if changed."""
|
||||||
if inner_to_s == to_s:
|
if inner_to_s.state == to_s.state:
|
||||||
return
|
return
|
||||||
hass.bus.remove_listener(EVENT_TIME_CHANGED,
|
hass.bus.remove_listener(EVENT_TIME_CHANGED,
|
||||||
attached_state_for_listener)
|
attached_state_for_listener)
|
||||||
@ -114,20 +83,3 @@ def trigger(hass, config, action):
|
|||||||
hass, entity_id, state_automation_listener, from_state, to_state)
|
hass, entity_id, state_automation_listener, from_state, to_state)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config):
|
|
||||||
"""Wrap action method with state based condition."""
|
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
|
||||||
state = config.get(CONF_STATE)
|
|
||||||
time_delta = get_time_config(config)
|
|
||||||
|
|
||||||
def if_state(variables):
|
|
||||||
"""Test if condition."""
|
|
||||||
is_state = hass.states.is_state(entity_id, state)
|
|
||||||
return (time_delta is None and is_state or
|
|
||||||
time_delta is not None and
|
|
||||||
dt_util.utcnow() - time_delta >
|
|
||||||
hass.states.get(entity_id).last_changed)
|
|
||||||
|
|
||||||
return if_state
|
|
||||||
|
@ -10,8 +10,6 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
from homeassistant.components import sun
|
|
||||||
from homeassistant.helpers.event import track_sunrise, track_sunset
|
from homeassistant.helpers.event import track_sunrise, track_sunset
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -29,26 +27,13 @@ EVENT_SUNRISE = 'sunrise'
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_SUN_EVENT = vol.All(vol.Lower, vol.Any(EVENT_SUNRISE, EVENT_SUNSET))
|
|
||||||
|
|
||||||
TRIGGER_SCHEMA = vol.Schema({
|
TRIGGER_SCHEMA = vol.Schema({
|
||||||
vol.Required(CONF_PLATFORM): 'sun',
|
vol.Required(CONF_PLATFORM): 'sun',
|
||||||
vol.Required(CONF_EVENT): _SUN_EVENT,
|
vol.Required(CONF_EVENT):
|
||||||
|
vol.All(vol.Lower, vol.Any(EVENT_SUNRISE, EVENT_SUNSET)),
|
||||||
vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period,
|
vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period,
|
||||||
})
|
})
|
||||||
|
|
||||||
IF_ACTION_SCHEMA = vol.All(
|
|
||||||
vol.Schema({
|
|
||||||
vol.Required(CONF_PLATFORM): 'sun',
|
|
||||||
CONF_BEFORE: _SUN_EVENT,
|
|
||||||
CONF_AFTER: _SUN_EVENT,
|
|
||||||
vol.Required(CONF_BEFORE_OFFSET, default=timedelta(0)): cv.time_period,
|
|
||||||
vol.Required(CONF_AFTER_OFFSET, default=timedelta(0)): cv.time_period,
|
|
||||||
}),
|
|
||||||
cv.has_at_least_one_key(CONF_BEFORE, CONF_AFTER),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
"""Listen for events based on configuration."""
|
"""Listen for events based on configuration."""
|
||||||
@ -72,55 +57,3 @@ def trigger(hass, config, action):
|
|||||||
track_sunset(hass, call_action, offset)
|
track_sunset(hass, call_action, offset)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config):
|
|
||||||
"""Wrap action method with sun based condition."""
|
|
||||||
before = config.get(CONF_BEFORE)
|
|
||||||
after = config.get(CONF_AFTER)
|
|
||||||
before_offset = config.get(CONF_BEFORE_OFFSET)
|
|
||||||
after_offset = config.get(CONF_AFTER_OFFSET)
|
|
||||||
|
|
||||||
if before is None:
|
|
||||||
def before_func():
|
|
||||||
"""Return no point in time."""
|
|
||||||
return None
|
|
||||||
elif before == EVENT_SUNRISE:
|
|
||||||
def before_func():
|
|
||||||
"""Return time before sunrise."""
|
|
||||||
return sun.next_rising(hass) + before_offset
|
|
||||||
else:
|
|
||||||
def before_func():
|
|
||||||
"""Return time before sunset."""
|
|
||||||
return sun.next_setting(hass) + before_offset
|
|
||||||
|
|
||||||
if after is None:
|
|
||||||
def after_func():
|
|
||||||
"""Return no point in time."""
|
|
||||||
return None
|
|
||||||
elif after == EVENT_SUNRISE:
|
|
||||||
def after_func():
|
|
||||||
"""Return time after sunrise."""
|
|
||||||
return sun.next_rising(hass) + after_offset
|
|
||||||
else:
|
|
||||||
def after_func():
|
|
||||||
"""Return time after sunset."""
|
|
||||||
return sun.next_setting(hass) + after_offset
|
|
||||||
|
|
||||||
def time_if(variables):
|
|
||||||
"""Validate time based if-condition."""
|
|
||||||
now = dt_util.now()
|
|
||||||
before = before_func()
|
|
||||||
after = after_func()
|
|
||||||
|
|
||||||
if before is not None and now > now.replace(hour=before.hour,
|
|
||||||
minute=before.minute):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if after is not None and now < now.replace(hour=after.hour,
|
|
||||||
minute=after.minute):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return time_if
|
|
||||||
|
@ -10,8 +10,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
|
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.helpers import condition
|
||||||
from homeassistant.helpers import template
|
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ def trigger(hass, config, action):
|
|||||||
def state_changed_listener(entity_id, from_s, to_s):
|
def state_changed_listener(entity_id, from_s, to_s):
|
||||||
"""Listen for state changes and calls action."""
|
"""Listen for state changes and calls action."""
|
||||||
nonlocal already_triggered
|
nonlocal already_triggered
|
||||||
template_result = _check_template(hass, value_template)
|
template_result = condition.template(hass, value_template)
|
||||||
|
|
||||||
# Check to see if template returns true
|
# Check to see if template returns true
|
||||||
if template_result and not already_triggered:
|
if template_result and not already_triggered:
|
||||||
@ -52,27 +51,3 @@ def trigger(hass, config, action):
|
|||||||
|
|
||||||
track_state_change(hass, MATCH_ALL, state_changed_listener)
|
track_state_change(hass, MATCH_ALL, state_changed_listener)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config):
|
|
||||||
"""Wrap action method with state based condition."""
|
|
||||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
|
||||||
|
|
||||||
return lambda variables: _check_template(hass, value_template,
|
|
||||||
variables=variables)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_template(hass, value_template, variables=None):
|
|
||||||
"""Check if result of template is true."""
|
|
||||||
try:
|
|
||||||
value = template.render(hass, value_template, variables)
|
|
||||||
except TemplateError as ex:
|
|
||||||
if ex.args and ex.args[0].startswith(
|
|
||||||
"UndefinedError: 'None' has no attribute"):
|
|
||||||
# Common during HA startup - so just a warning
|
|
||||||
_LOGGER.warning(ex)
|
|
||||||
else:
|
|
||||||
_LOGGER.error(ex)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return value.lower() == 'true'
|
|
||||||
|
@ -6,38 +6,38 @@ at https://home-assistant.io/components/automation/#time-trigger
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.event import track_time_change
|
from homeassistant.helpers.event import track_time_change
|
||||||
|
|
||||||
CONF_HOURS = "hours"
|
CONF_HOURS = "hours"
|
||||||
CONF_MINUTES = "minutes"
|
CONF_MINUTES = "minutes"
|
||||||
CONF_SECONDS = "seconds"
|
CONF_SECONDS = "seconds"
|
||||||
CONF_BEFORE = "before"
|
|
||||||
CONF_AFTER = "after"
|
CONF_AFTER = "after"
|
||||||
CONF_WEEKDAY = "weekday"
|
|
||||||
|
|
||||||
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||||
|
vol.Required(CONF_PLATFORM): 'time',
|
||||||
|
CONF_AFTER: cv.time,
|
||||||
|
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||||
|
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||||
|
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||||
|
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES,
|
||||||
|
CONF_SECONDS, CONF_AFTER))
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
if CONF_AFTER in config:
|
if CONF_AFTER in config:
|
||||||
after = dt_util.parse_time(config[CONF_AFTER])
|
after = config.get(CONF_AFTER)
|
||||||
if after is None:
|
|
||||||
_error_time(config[CONF_AFTER], CONF_AFTER)
|
|
||||||
return False
|
|
||||||
hours, minutes, seconds = after.hour, after.minute, after.second
|
hours, minutes, seconds = after.hour, after.minute, after.second
|
||||||
elif (CONF_HOURS in config or CONF_MINUTES in config or
|
else:
|
||||||
CONF_SECONDS in config):
|
|
||||||
hours = config.get(CONF_HOURS)
|
hours = config.get(CONF_HOURS)
|
||||||
minutes = config.get(CONF_MINUTES)
|
minutes = config.get(CONF_MINUTES)
|
||||||
seconds = config.get(CONF_SECONDS)
|
seconds = config.get(CONF_SECONDS)
|
||||||
else:
|
|
||||||
_LOGGER.error('One of %s, %s, %s OR %s needs to be specified',
|
|
||||||
CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AFTER)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def time_automation_listener(now):
|
def time_automation_listener(now):
|
||||||
"""Listen for time changes and calls action."""
|
"""Listen for time changes and calls action."""
|
||||||
@ -52,58 +52,3 @@ def trigger(hass, config, action):
|
|||||||
hour=hours, minute=minutes, second=seconds)
|
hour=hours, minute=minutes, second=seconds)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config):
|
|
||||||
"""Wrap action method with time based condition."""
|
|
||||||
before = config.get(CONF_BEFORE)
|
|
||||||
after = config.get(CONF_AFTER)
|
|
||||||
weekday = config.get(CONF_WEEKDAY)
|
|
||||||
|
|
||||||
if before is None and after is None and weekday is None:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Missing if-condition configuration key %s, %s or %s",
|
|
||||||
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if before is not None:
|
|
||||||
before = dt_util.parse_time(before)
|
|
||||||
if before is None:
|
|
||||||
_error_time(before, CONF_BEFORE)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if after is not None:
|
|
||||||
after = dt_util.parse_time(after)
|
|
||||||
if after is None:
|
|
||||||
_error_time(after, CONF_AFTER)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def time_if(variables):
|
|
||||||
"""Validate time based if-condition."""
|
|
||||||
now = dt_util.now()
|
|
||||||
if before is not None and now > now.replace(hour=before.hour,
|
|
||||||
minute=before.minute):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if after is not None and now < now.replace(hour=after.hour,
|
|
||||||
minute=after.minute):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if weekday is not None:
|
|
||||||
now_weekday = WEEKDAYS[now.weekday()]
|
|
||||||
|
|
||||||
if isinstance(weekday, str) and weekday != now_weekday or \
|
|
||||||
now_weekday not in weekday:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return time_if
|
|
||||||
|
|
||||||
|
|
||||||
def _error_time(value, key):
|
|
||||||
"""Helper method to print error."""
|
|
||||||
_LOGGER.error(
|
|
||||||
"Received invalid value for '%s': %s", key, value)
|
|
||||||
if isinstance(value, int):
|
|
||||||
_LOGGER.error('Make sure you wrap time values in quotes')
|
|
||||||
|
@ -6,11 +6,10 @@ at https://home-assistant.io/components/automation/#zone-trigger
|
|||||||
"""
|
"""
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import zone
|
from homeassistant.const import MATCH_ALL, CONF_PLATFORM
|
||||||
from homeassistant.const import (
|
|
||||||
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL, CONF_PLATFORM)
|
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.helpers import (
|
||||||
|
condition, config_validation as cv, location)
|
||||||
|
|
||||||
CONF_ENTITY_ID = "entity_id"
|
CONF_ENTITY_ID = "entity_id"
|
||||||
CONF_ZONE = "zone"
|
CONF_ZONE = "zone"
|
||||||
@ -27,12 +26,6 @@ TRIGGER_SCHEMA = vol.Schema({
|
|||||||
vol.Any(EVENT_ENTER, EVENT_LEAVE),
|
vol.Any(EVENT_ENTER, EVENT_LEAVE),
|
||||||
})
|
})
|
||||||
|
|
||||||
IF_ACTION_SCHEMA = vol.Schema({
|
|
||||||
vol.Required(CONF_PLATFORM): 'zone',
|
|
||||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
|
||||||
vol.Required(CONF_ZONE): cv.entity_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def trigger(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
@ -42,15 +35,16 @@ def trigger(hass, config, action):
|
|||||||
|
|
||||||
def zone_automation_listener(entity, from_s, to_s):
|
def zone_automation_listener(entity, from_s, to_s):
|
||||||
"""Listen for state changes and calls action."""
|
"""Listen for state changes and calls action."""
|
||||||
if from_s and None in (from_s.attributes.get(ATTR_LATITUDE),
|
if from_s and not location.has_location(from_s) or \
|
||||||
from_s.attributes.get(ATTR_LONGITUDE)) or \
|
not location.has_location(to_s):
|
||||||
None in (to_s.attributes.get(ATTR_LATITUDE),
|
|
||||||
to_s.attributes.get(ATTR_LONGITUDE)):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
zone_state = hass.states.get(zone_entity_id)
|
zone_state = hass.states.get(zone_entity_id)
|
||||||
from_match = _in_zone(hass, zone_state, from_s) if from_s else None
|
if from_s:
|
||||||
to_match = _in_zone(hass, zone_state, to_s)
|
from_match = condition.zone(hass, zone_state, from_s)
|
||||||
|
else:
|
||||||
|
from_match = False
|
||||||
|
to_match = condition.zone(hass, zone_state, to_s)
|
||||||
|
|
||||||
# pylint: disable=too-many-boolean-expressions
|
# pylint: disable=too-many-boolean-expressions
|
||||||
if event == EVENT_ENTER and not from_match and to_match or \
|
if event == EVENT_ENTER and not from_match and to_match or \
|
||||||
@ -69,28 +63,3 @@ def trigger(hass, config, action):
|
|||||||
hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL)
|
hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def if_action(hass, config):
|
|
||||||
"""Wrap action method with zone based condition."""
|
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
|
||||||
zone_entity_id = config.get(CONF_ZONE)
|
|
||||||
|
|
||||||
def if_in_zone(variables):
|
|
||||||
"""Test if condition."""
|
|
||||||
zone_state = hass.states.get(zone_entity_id)
|
|
||||||
return _in_zone(hass, zone_state, hass.states.get(entity_id))
|
|
||||||
|
|
||||||
return if_in_zone
|
|
||||||
|
|
||||||
|
|
||||||
def _in_zone(hass, zone_state, state):
|
|
||||||
"""Check if state is in zone."""
|
|
||||||
if not state or None in (state.attributes.get(ATTR_LATITUDE),
|
|
||||||
state.attributes.get(ATTR_LONGITUDE)):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return zone_state and zone.in_zone(
|
|
||||||
zone_state, state.attributes.get(ATTR_LATITUDE),
|
|
||||||
state.attributes.get(ATTR_LONGITUDE),
|
|
||||||
state.attributes.get(ATTR_GPS_ACCURACY, 0))
|
|
||||||
|
@ -12,6 +12,8 @@ MATCH_ALL = '*'
|
|||||||
# If no name is specified
|
# If no name is specified
|
||||||
DEVICE_DEFAULT_NAME = "Unnamed Device"
|
DEVICE_DEFAULT_NAME = "Unnamed Device"
|
||||||
|
|
||||||
|
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
||||||
|
|
||||||
# #### CONFIG ####
|
# #### CONFIG ####
|
||||||
CONF_ALIAS = "alias"
|
CONF_ALIAS = "alias"
|
||||||
CONF_ICON = "icon"
|
CONF_ICON = "icon"
|
||||||
@ -34,9 +36,13 @@ CONF_ACCESS_TOKEN = "access_token"
|
|||||||
CONF_FILENAME = "filename"
|
CONF_FILENAME = "filename"
|
||||||
CONF_MONITORED_CONDITIONS = 'monitored_conditions'
|
CONF_MONITORED_CONDITIONS = 'monitored_conditions'
|
||||||
CONF_OPTIMISTIC = 'optimistic'
|
CONF_OPTIMISTIC = 'optimistic'
|
||||||
|
CONF_ENTITY_ID = "entity_id"
|
||||||
CONF_ENTITY_NAMESPACE = "entity_namespace"
|
CONF_ENTITY_NAMESPACE = "entity_namespace"
|
||||||
CONF_SCAN_INTERVAL = "scan_interval"
|
CONF_SCAN_INTERVAL = "scan_interval"
|
||||||
CONF_VALUE_TEMPLATE = "value_template"
|
CONF_VALUE_TEMPLATE = "value_template"
|
||||||
|
CONF_CONDITION = 'condition'
|
||||||
|
CONF_BELOW = 'below'
|
||||||
|
CONF_ABOVE = 'above'
|
||||||
|
|
||||||
# #### EVENTS ####
|
# #### EVENTS ####
|
||||||
EVENT_HOMEASSISTANT_START = "homeassistant_start"
|
EVENT_HOMEASSISTANT_START = "homeassistant_start"
|
||||||
|
290
homeassistant/helpers/condition.py
Normal file
290
homeassistant/helpers/condition.py
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
"""Offer reusable conditions."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from homeassistant.components import (
|
||||||
|
zone as zone_cmp, sun as sun_cmp)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE,
|
||||||
|
CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, CONF_CONDITION,
|
||||||
|
WEEKDAYS)
|
||||||
|
from homeassistant.exceptions import TemplateError, HomeAssistantError
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.template import render
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
FROM_CONFIG_FORMAT = '{}_from_config'
|
||||||
|
CONF_BELOW = 'below'
|
||||||
|
CONF_ABOVE = 'above'
|
||||||
|
CONF_STATE = 'state'
|
||||||
|
CONF_ZONE = 'zone'
|
||||||
|
|
||||||
|
EVENT_SUNRISE = 'sunrise'
|
||||||
|
EVENT_SUNSET = 'sunset'
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def from_config(config, config_validation=True):
|
||||||
|
"""Turn a condition configuration into a method."""
|
||||||
|
factory = getattr(
|
||||||
|
sys.modules[__name__],
|
||||||
|
FROM_CONFIG_FORMAT.format(config.get(CONF_CONDITION)), None)
|
||||||
|
|
||||||
|
if factory is None:
|
||||||
|
raise HomeAssistantError('Invalid condition "{}" specified {}'.format(
|
||||||
|
config.get(CONF_CONDITION), config))
|
||||||
|
|
||||||
|
return factory(config, config_validation)
|
||||||
|
|
||||||
|
|
||||||
|
def and_from_config(config, config_validation=True):
|
||||||
|
"""Create multi condition matcher using 'AND'."""
|
||||||
|
if config_validation:
|
||||||
|
config = cv.AND_CONDITION_SCHEMA(config)
|
||||||
|
checks = [from_config(entry) for entry in config['conditions']]
|
||||||
|
|
||||||
|
def if_and_condition(hass, variables=None):
|
||||||
|
"""Test and condition."""
|
||||||
|
return all(check(hass, variables) for check in checks)
|
||||||
|
|
||||||
|
return if_and_condition
|
||||||
|
|
||||||
|
|
||||||
|
def or_from_config(config, config_validation=True):
|
||||||
|
"""Create multi condition matcher using 'OR'."""
|
||||||
|
if config_validation:
|
||||||
|
config = cv.OR_CONDITION_SCHEMA(config)
|
||||||
|
checks = [from_config(entry) for entry in config['conditions']]
|
||||||
|
|
||||||
|
def if_or_condition(hass, variables=None):
|
||||||
|
"""Test and condition."""
|
||||||
|
return any(check(hass, variables) for check in checks)
|
||||||
|
|
||||||
|
return if_or_condition
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def numeric_state(hass, entity, below=None, above=None, value_template=None,
|
||||||
|
variables=None):
|
||||||
|
"""Test a numeric state condition."""
|
||||||
|
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 {})
|
||||||
|
variables['state'] = entity
|
||||||
|
try:
|
||||||
|
value = render(hass, value_template, variables)
|
||||||
|
except TemplateError as ex:
|
||||||
|
_LOGGER.error(ex)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = float(value)
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.warning("Value cannot be processed as a number: %s", value)
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(below, value, above)
|
||||||
|
|
||||||
|
if below is not None and value > below:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if above is not None and value < above:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def numeric_state_from_config(config, config_validation=True):
|
||||||
|
"""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)
|
||||||
|
|
||||||
|
def if_numeric_state(hass, variables=None):
|
||||||
|
"""Test numeric state condition."""
|
||||||
|
return numeric_state(hass, entity_id, below, above, value_template,
|
||||||
|
variables)
|
||||||
|
|
||||||
|
return if_numeric_state
|
||||||
|
|
||||||
|
|
||||||
|
def state(hass, entity, req_state, for_period=None):
|
||||||
|
"""Test if state matches requirements."""
|
||||||
|
if isinstance(entity, str):
|
||||||
|
entity = hass.states.get(entity)
|
||||||
|
|
||||||
|
if entity is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def state_from_config(config, config_validation=True):
|
||||||
|
"""Wrap action method with state based condition."""
|
||||||
|
if config_validation:
|
||||||
|
config = cv.STATE_CONDITION_SCHEMA(config)
|
||||||
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
|
req_state = config.get(CONF_STATE)
|
||||||
|
for_period = config.get('for')
|
||||||
|
|
||||||
|
def if_state(hass, variables=None):
|
||||||
|
"""Test if condition."""
|
||||||
|
return state(hass, entity_id, req_state, for_period)
|
||||||
|
|
||||||
|
return if_state
|
||||||
|
|
||||||
|
|
||||||
|
def sun(hass, before=None, after=None, before_offset=None, after_offset=None):
|
||||||
|
"""Test if current time matches sun requirements."""
|
||||||
|
now = dt_util.now().time()
|
||||||
|
before_offset = before_offset or timedelta(0)
|
||||||
|
after_offset = after_offset or timedelta(0)
|
||||||
|
|
||||||
|
if before == EVENT_SUNRISE and now > (sun_cmp.next_rising(hass) +
|
||||||
|
before_offset).time():
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif before == EVENT_SUNSET and now > (sun_cmp.next_setting(hass) +
|
||||||
|
before_offset).time():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if after == EVENT_SUNRISE and now < (sun_cmp.next_rising(hass) +
|
||||||
|
after_offset).time():
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif after == EVENT_SUNSET and now < (sun_cmp.next_setting(hass) +
|
||||||
|
after_offset).time():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def sun_from_config(config, config_validation=True):
|
||||||
|
"""Wrap action method with sun based condition."""
|
||||||
|
if config_validation:
|
||||||
|
config = cv.SUN_CONDITION_SCHEMA(config)
|
||||||
|
before = config.get('before')
|
||||||
|
after = config.get('after')
|
||||||
|
before_offset = config.get('before_offset')
|
||||||
|
after_offset = config.get('after_offset')
|
||||||
|
|
||||||
|
def time_if(hass, variables=None):
|
||||||
|
"""Validate time based if-condition."""
|
||||||
|
return sun(hass, before, after, before_offset, after_offset)
|
||||||
|
|
||||||
|
return time_if
|
||||||
|
|
||||||
|
|
||||||
|
def template(hass, value_template, variables=None):
|
||||||
|
"""Test if template condition matches."""
|
||||||
|
try:
|
||||||
|
value = render(hass, value_template, variables)
|
||||||
|
except TemplateError as ex:
|
||||||
|
_LOGGER.error('Error duriong template condition: %s', ex)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return value.lower() == 'true'
|
||||||
|
|
||||||
|
|
||||||
|
def template_from_config(config, config_validation=True):
|
||||||
|
"""Wrap action method with state based condition."""
|
||||||
|
if config_validation:
|
||||||
|
config = cv.TEMPLATE_CONDITION_SCHEMA(config)
|
||||||
|
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
|
def template_if(hass, variables=None):
|
||||||
|
"""Validate template based if-condition."""
|
||||||
|
return template(hass, value_template, variables)
|
||||||
|
|
||||||
|
return template_if
|
||||||
|
|
||||||
|
|
||||||
|
def time(before=None, after=None, weekday=None):
|
||||||
|
"""Test if local time condition matches."""
|
||||||
|
now = dt_util.now()
|
||||||
|
now_time = now.time()
|
||||||
|
|
||||||
|
if before is not None and now_time > before:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if after is not None and now_time < after:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if weekday is not None:
|
||||||
|
now_weekday = WEEKDAYS[now.weekday()]
|
||||||
|
|
||||||
|
if isinstance(weekday, str) and weekday != now_weekday or \
|
||||||
|
now_weekday not in weekday:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def time_from_config(config, config_validation=True):
|
||||||
|
"""Wrap action method with time based condition."""
|
||||||
|
if config_validation:
|
||||||
|
config = cv.TIME_CONDITION_SCHEMA(config)
|
||||||
|
before = config.get('before')
|
||||||
|
after = config.get('after')
|
||||||
|
weekday = config.get('weekday')
|
||||||
|
|
||||||
|
def time_if(hass, variables=None):
|
||||||
|
"""Validate time based if-condition."""
|
||||||
|
return time(before, after, weekday)
|
||||||
|
|
||||||
|
return time_if
|
||||||
|
|
||||||
|
|
||||||
|
def zone(hass, zone_ent, entity):
|
||||||
|
"""Test if zone-condition matches."""
|
||||||
|
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
|
||||||
|
|
||||||
|
return zone_cmp.in_zone(zone_ent, latitude, longitude,
|
||||||
|
entity.attributes.get(ATTR_GPS_ACCURACY, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def zone_from_config(config, config_validation=True):
|
||||||
|
"""Wrap action method with zone based condition."""
|
||||||
|
if config_validation:
|
||||||
|
config = cv.ZONE_CONDITION_SCHEMA(config)
|
||||||
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
|
zone_entity_id = config.get('zone')
|
||||||
|
|
||||||
|
def if_in_zone(hass, variables=None):
|
||||||
|
"""Test if condition."""
|
||||||
|
return zone(hass, zone_entity_id, entity_id)
|
||||||
|
|
||||||
|
return if_in_zone
|
@ -7,13 +7,16 @@ import voluptuous as vol
|
|||||||
from homeassistant.loader import get_platform
|
from homeassistant.loader import get_platform
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||||
CONF_ALIAS)
|
CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS,
|
||||||
|
CONF_CONDITION, CONF_BELOW, CONF_ABOVE)
|
||||||
from homeassistant.helpers.entity import valid_entity_id
|
from homeassistant.helpers.entity import valid_entity_id
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
|
TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'"
|
||||||
|
|
||||||
# Home Assistant types
|
# Home Assistant types
|
||||||
byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255))
|
byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255))
|
||||||
small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1))
|
small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1))
|
||||||
@ -105,9 +108,10 @@ time_period_dict = vol.All(
|
|||||||
|
|
||||||
def time_period_str(value):
|
def time_period_str(value):
|
||||||
"""Validate and transform time offset."""
|
"""Validate and transform time offset."""
|
||||||
if not isinstance(value, str):
|
if isinstance(value, int):
|
||||||
raise vol.Invalid(
|
raise vol.Invalid('Make sure you wrap time values in quotes')
|
||||||
'offset {} should be format HH:MM or HH:MM:SS'.format(value))
|
elif not isinstance(value, str):
|
||||||
|
raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
|
||||||
|
|
||||||
negative_offset = False
|
negative_offset = False
|
||||||
if value.startswith('-'):
|
if value.startswith('-'):
|
||||||
@ -119,8 +123,7 @@ def time_period_str(value):
|
|||||||
try:
|
try:
|
||||||
parsed = [int(x) for x in value.split(':')]
|
parsed = [int(x) for x in value.split(':')]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
|
||||||
'offset {} should be format HH:MM or HH:MM:SS'.format(value))
|
|
||||||
|
|
||||||
if len(parsed) == 2:
|
if len(parsed) == 2:
|
||||||
hour, minute = parsed
|
hour, minute = parsed
|
||||||
@ -128,8 +131,7 @@ def time_period_str(value):
|
|||||||
elif len(parsed) == 3:
|
elif len(parsed) == 3:
|
||||||
hour, minute, second = parsed
|
hour, minute, second = parsed
|
||||||
else:
|
else:
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(TIME_PERIOD_ERROR.format(value))
|
||||||
'offset {} should be format HH:MM or HH:MM:SS'.format(value))
|
|
||||||
|
|
||||||
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
offset = timedelta(hours=hour, minutes=minute, seconds=second)
|
||||||
|
|
||||||
@ -217,6 +219,16 @@ def template(value):
|
|||||||
raise vol.Invalid('invalid template ({})'.format(ex))
|
raise vol.Invalid('invalid template ({})'.format(ex))
|
||||||
|
|
||||||
|
|
||||||
|
def time(value):
|
||||||
|
"""Validate time."""
|
||||||
|
time_val = dt_util.parse_time(value)
|
||||||
|
|
||||||
|
if time_val is None:
|
||||||
|
raise vol.Invalid('Invalid time specified: {}'.format(value))
|
||||||
|
|
||||||
|
return time_val
|
||||||
|
|
||||||
|
|
||||||
def time_zone(value):
|
def time_zone(value):
|
||||||
"""Validate timezone."""
|
"""Validate timezone."""
|
||||||
if dt_util.get_time_zone(value) is not None:
|
if dt_util.get_time_zone(value) is not None:
|
||||||
@ -225,6 +237,8 @@ def time_zone(value):
|
|||||||
'Invalid time zone passed in. Valid options can be found here: '
|
'Invalid time zone passed in. Valid options can be found here: '
|
||||||
'http://en.wikipedia.org/wiki/List_of_tz_database_time_zones')
|
'http://en.wikipedia.org/wiki/List_of_tz_database_time_zones')
|
||||||
|
|
||||||
|
weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)])
|
||||||
|
|
||||||
|
|
||||||
# Validator helpers
|
# Validator helpers
|
||||||
|
|
||||||
@ -261,9 +275,83 @@ SERVICE_SCHEMA = vol.All(vol.Schema({
|
|||||||
vol.Exclusive('service_template', 'service name'): template,
|
vol.Exclusive('service_template', 'service name'): template,
|
||||||
vol.Optional('data'): dict,
|
vol.Optional('data'): dict,
|
||||||
vol.Optional('data_template'): {match_all: template},
|
vol.Optional('data_template'): {match_all: template},
|
||||||
vol.Optional('entity_id'): entity_ids,
|
vol.Optional(CONF_ENTITY_ID): entity_ids,
|
||||||
}), has_at_least_one_key('service', 'service_template'))
|
}), has_at_least_one_key('service', 'service_template'))
|
||||||
|
|
||||||
|
NUMERIC_STATE_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'numeric_state',
|
||||||
|
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||||
|
CONF_BELOW: vol.Coerce(float),
|
||||||
|
CONF_ABOVE: vol.Coerce(float),
|
||||||
|
vol.Optional(CONF_VALUE_TEMPLATE): template,
|
||||||
|
}), has_at_least_one_key(CONF_BELOW, CONF_ABOVE))
|
||||||
|
|
||||||
|
STATE_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'state',
|
||||||
|
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||||
|
vol.Required('state'): str,
|
||||||
|
vol.Optional('for'): vol.All(time_period, positive_timedelta),
|
||||||
|
# To support use_trigger_value in automation
|
||||||
|
# Deprecated 2016/04/25
|
||||||
|
vol.Optional('from'): str,
|
||||||
|
}), key_dependency('for', 'state'))
|
||||||
|
|
||||||
|
SUN_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'sun',
|
||||||
|
vol.Optional('before'): vol.Any('sunset', 'sunrise'),
|
||||||
|
vol.Optional('before_offset'): time_period,
|
||||||
|
vol.Optional('after'): vol.All(vol.Lower, vol.Any('sunset', 'sunrise')),
|
||||||
|
vol.Optional('after_offset'): time_period,
|
||||||
|
}), has_at_least_one_key('before', 'after'))
|
||||||
|
|
||||||
|
TEMPLATE_CONDITION_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'template',
|
||||||
|
vol.Required(CONF_VALUE_TEMPLATE): template,
|
||||||
|
})
|
||||||
|
|
||||||
|
TIME_CONDITION_SCHEMA = vol.All(vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'time',
|
||||||
|
'before': time,
|
||||||
|
'after': time,
|
||||||
|
'weekday': weekdays,
|
||||||
|
}), has_at_least_one_key('before', 'after', 'weekday'))
|
||||||
|
|
||||||
|
ZONE_CONDITION_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'zone',
|
||||||
|
vol.Required(CONF_ENTITY_ID): entity_id,
|
||||||
|
'zone': entity_id,
|
||||||
|
# To support use_trigger_value in automation
|
||||||
|
# Deprecated 2016/04/25
|
||||||
|
vol.Optional('event'): vol.Any('enter', 'leave'),
|
||||||
|
})
|
||||||
|
|
||||||
|
AND_CONDITION_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'and',
|
||||||
|
vol.Required('conditions'): vol.All(
|
||||||
|
ensure_list,
|
||||||
|
# pylint: disable=unnecessary-lambda
|
||||||
|
[lambda value: CONDITION_SCHEMA(value)],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
OR_CONDITION_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_CONDITION): 'or',
|
||||||
|
vol.Required('conditions'): vol.All(
|
||||||
|
ensure_list,
|
||||||
|
# pylint: disable=unnecessary-lambda
|
||||||
|
[lambda value: CONDITION_SCHEMA(value)],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
CONDITION_SCHEMA = vol.Any(
|
||||||
|
NUMERIC_STATE_CONDITION_SCHEMA,
|
||||||
|
STATE_CONDITION_SCHEMA,
|
||||||
|
SUN_CONDITION_SCHEMA,
|
||||||
|
TEMPLATE_CONDITION_SCHEMA,
|
||||||
|
TIME_CONDITION_SCHEMA,
|
||||||
|
ZONE_CONDITION_SCHEMA,
|
||||||
|
)
|
||||||
|
|
||||||
_SCRIPT_DELAY_SCHEMA = vol.Schema({
|
_SCRIPT_DELAY_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_ALIAS): string,
|
vol.Optional(CONF_ALIAS): string,
|
||||||
vol.Required("delay"): vol.All(time_period, positive_timedelta)
|
vol.Required("delay"): vol.All(time_period, positive_timedelta)
|
||||||
@ -271,5 +359,6 @@ _SCRIPT_DELAY_SCHEMA = vol.Schema({
|
|||||||
|
|
||||||
SCRIPT_SCHEMA = vol.All(
|
SCRIPT_SCHEMA = vol.All(
|
||||||
ensure_list,
|
ensure_list,
|
||||||
[vol.Any(SERVICE_SCHEMA, _SCRIPT_DELAY_SCHEMA, EVENT_SCHEMA)],
|
[vol.Any(SERVICE_SCHEMA, _SCRIPT_DELAY_SCHEMA, EVENT_SCHEMA,
|
||||||
|
CONDITION_SCHEMA)],
|
||||||
)
|
)
|
||||||
|
@ -4,9 +4,9 @@ import threading
|
|||||||
from itertools import islice
|
from itertools import islice
|
||||||
|
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
from homeassistant.const import EVENT_TIME_CHANGED
|
from homeassistant.const import EVENT_TIME_CHANGED, CONF_CONDITION
|
||||||
from homeassistant.helpers.event import track_point_in_utc_time
|
from homeassistant.helpers.event import track_point_in_utc_time
|
||||||
from homeassistant.helpers import service
|
from homeassistant.helpers import service, condition
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -76,6 +76,10 @@ class Script():
|
|||||||
self._change_listener()
|
self._change_listener()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
elif CONF_CONDITION in action:
|
||||||
|
if not self._check_condition(action, variables):
|
||||||
|
break
|
||||||
|
|
||||||
elif CONF_EVENT in action:
|
elif CONF_EVENT in action:
|
||||||
self._fire_event(action)
|
self._fire_event(action)
|
||||||
|
|
||||||
@ -111,6 +115,13 @@ class Script():
|
|||||||
self._log("Executing step %s", self.last_action)
|
self._log("Executing step %s", self.last_action)
|
||||||
self.hass.bus.fire(action[CONF_EVENT], action.get(CONF_EVENT_DATA))
|
self.hass.bus.fire(action[CONF_EVENT], action.get(CONF_EVENT_DATA))
|
||||||
|
|
||||||
|
def _check_condition(self, action, variables):
|
||||||
|
"""Test if condition is matching."""
|
||||||
|
self.last_action = action.get(CONF_ALIAS, action[CONF_CONDITION])
|
||||||
|
check = condition.from_config(action)(self.hass, False)
|
||||||
|
self._log("Test condition %s: %s", self.last_action, check)
|
||||||
|
return check
|
||||||
|
|
||||||
def _remove_listener(self):
|
def _remove_listener(self):
|
||||||
"""Remove point in time listener, if any."""
|
"""Remove point in time listener, if any."""
|
||||||
if self._delay_listener:
|
if self._delay_listener:
|
||||||
|
@ -33,8 +33,8 @@ def render_with_possible_json_value(hass, template, value,
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
return render(hass, template, variables)
|
return render(hass, template, variables)
|
||||||
except TemplateError:
|
except TemplateError as ex:
|
||||||
_LOGGER.exception('Error parsing value')
|
_LOGGER.error('Error parsing value: %s', ex)
|
||||||
return value if error_value is _SENTINEL else error_value
|
return value if error_value is _SENTINEL else error_value
|
||||||
|
|
||||||
|
|
||||||
|
@ -146,12 +146,12 @@ class TestAutomation(unittest.TestCase):
|
|||||||
],
|
],
|
||||||
'condition': [
|
'condition': [
|
||||||
{
|
{
|
||||||
'platform': 'state',
|
'condition': 'state',
|
||||||
'entity_id': entity_id,
|
'entity_id': entity_id,
|
||||||
'state': '100'
|
'state': '100'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'platform': 'numeric_state',
|
'condition': 'numeric_state',
|
||||||
'entity_id': entity_id,
|
'entity_id': entity_id,
|
||||||
'below': 150
|
'below': 150
|
||||||
}
|
}
|
||||||
@ -231,6 +231,7 @@ class TestAutomation(unittest.TestCase):
|
|||||||
{
|
{
|
||||||
'platform': 'state',
|
'platform': 'state',
|
||||||
'entity_id': entity_id,
|
'entity_id': entity_id,
|
||||||
|
'from': '120',
|
||||||
'state': '100'
|
'state': '100'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -248,10 +249,14 @@ class TestAutomation(unittest.TestCase):
|
|||||||
|
|
||||||
self.hass.states.set(entity_id, 100)
|
self.hass.states.set(entity_id, 100)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(2, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
self.hass.states.set(entity_id, 120)
|
self.hass.states.set(entity_id, 120)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, 100)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(2, len(self.calls))
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
self.hass.states.set(entity_id, 151)
|
self.hass.states.set(entity_id, 151)
|
||||||
|
@ -253,6 +253,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
'trigger': {
|
'trigger': {
|
||||||
'platform': 'numeric_state',
|
'platform': 'numeric_state',
|
||||||
'entity_id': 'test.another_entity',
|
'entity_id': 'test.another_entity',
|
||||||
|
'below': 100,
|
||||||
},
|
},
|
||||||
'action': {
|
'action': {
|
||||||
'service': 'test.automation'
|
'service': 'test.automation'
|
||||||
@ -441,13 +442,11 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
'data_template': {
|
'data_template': {
|
||||||
'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join((
|
'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join((
|
||||||
'platform', 'entity_id', 'below', 'above',
|
'platform', 'entity_id', 'below', 'above',
|
||||||
'from_state.state', 'from_value',
|
'from_state.state', 'to_state.state'))
|
||||||
'to_state.state', 'to_value'))
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
# 9 is below 10
|
|
||||||
self.hass.states.set('test.entity', 'test state 1',
|
self.hass.states.set('test.entity', 'test state 1',
|
||||||
{'test_attribute': '1.2'})
|
{'test_attribute': '1.2'})
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -456,8 +455,8 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'numeric_state - test.entity - 10 - None - test state 1 - 12.0 - '
|
'numeric_state - test.entity - 10.0 - None - test state 1 - '
|
||||||
'test state 2 - 9.0',
|
'test state 2',
|
||||||
self.calls[0].data['some'])
|
self.calls[0].data['some'])
|
||||||
|
|
||||||
def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(self):
|
def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(self):
|
||||||
|
@ -40,7 +40,7 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
|
||||||
trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC)
|
trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
with patch('homeassistant.util.dt.utcnow',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
_setup_component(self.hass, automation.DOMAIN, {
|
_setup_component(self.hass, automation.DOMAIN, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
@ -67,7 +67,7 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
|
||||||
trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC)
|
trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
with patch('homeassistant.util.dt.utcnow',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
_setup_component(self.hass, automation.DOMAIN, {
|
_setup_component(self.hass, automation.DOMAIN, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
@ -94,7 +94,7 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
|
||||||
trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC)
|
trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
with patch('homeassistant.util.dt.utcnow',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
_setup_component(self.hass, automation.DOMAIN, {
|
_setup_component(self.hass, automation.DOMAIN, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
@ -128,7 +128,7 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
|
||||||
trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC)
|
trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.utcnow',
|
with patch('homeassistant.util.dt.utcnow',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
_setup_component(self.hass, automation.DOMAIN, {
|
_setup_component(self.hass, automation.DOMAIN, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
@ -170,14 +170,14 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -206,14 +206,14 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -243,14 +243,14 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -280,14 +280,14 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -318,21 +318,21 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC)
|
now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC)
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -364,7 +364,7 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
|
|
||||||
# Before
|
# Before
|
||||||
now = datetime(2015, 9, 16, 17, tzinfo=pytz.timezone('US/Mountain'))
|
now = datetime(2015, 9, 16, 17, tzinfo=pytz.timezone('US/Mountain'))
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -372,7 +372,7 @@ class TestAutomationSun(unittest.TestCase):
|
|||||||
|
|
||||||
# After
|
# After
|
||||||
now = datetime(2015, 9, 16, 18, tzinfo=pytz.timezone('US/Mountain'))
|
now = datetime(2015, 9, 16, 18, tzinfo=pytz.timezone('US/Mountain'))
|
||||||
with patch('homeassistant.components.automation.sun.dt_util.now',
|
with patch('homeassistant.util.dt.now',
|
||||||
return_value=now):
|
return_value=now):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
@ -194,7 +194,7 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
|
|
||||||
def test_if_not_working_if_no_values_in_conf_provided(self):
|
def test_if_not_working_if_no_values_in_conf_provided(self):
|
||||||
"""Test for failure if no configuration."""
|
"""Test for failure if no configuration."""
|
||||||
assert _setup_component(self.hass, automation.DOMAIN, {
|
assert not _setup_component(self.hass, automation.DOMAIN, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
'trigger': {
|
'trigger': {
|
||||||
'platform': 'time',
|
'platform': 'time',
|
||||||
@ -211,13 +211,12 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
@patch('homeassistant.components.automation.time._LOGGER.error')
|
def test_if_not_fires_using_wrong_after(self):
|
||||||
def test_if_not_fires_using_wrong_after(self, mock_error):
|
|
||||||
"""YAML translates time values to total seconds.
|
"""YAML translates time values to total seconds.
|
||||||
|
|
||||||
This should break the before rule.
|
This should break the before rule.
|
||||||
"""
|
"""
|
||||||
assert _setup_component(self.hass, automation.DOMAIN, {
|
assert not _setup_component(self.hass, automation.DOMAIN, {
|
||||||
automation.DOMAIN: {
|
automation.DOMAIN: {
|
||||||
'trigger': {
|
'trigger': {
|
||||||
'platform': 'time',
|
'platform': 'time',
|
||||||
@ -235,7 +234,6 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
|
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
self.assertEqual(2, mock_error.call_count)
|
|
||||||
|
|
||||||
def test_if_action_before(self):
|
def test_if_action_before(self):
|
||||||
"""Test for if action before."""
|
"""Test for if action before."""
|
||||||
@ -258,14 +256,14 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
before_10 = dt_util.now().replace(hour=8)
|
before_10 = dt_util.now().replace(hour=8)
|
||||||
after_10 = dt_util.now().replace(hour=14)
|
after_10 = dt_util.now().replace(hour=14)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=before_10):
|
return_value=before_10):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=after_10):
|
return_value=after_10):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -293,14 +291,14 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
before_10 = dt_util.now().replace(hour=8)
|
before_10 = dt_util.now().replace(hour=8)
|
||||||
after_10 = dt_util.now().replace(hour=14)
|
after_10 = dt_util.now().replace(hour=14)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=before_10):
|
return_value=before_10):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=after_10):
|
return_value=after_10):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -329,14 +327,14 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
monday = dt_util.now() - timedelta(days=days_past_monday)
|
monday = dt_util.now() - timedelta(days=days_past_monday)
|
||||||
tuesday = monday + timedelta(days=1)
|
tuesday = monday + timedelta(days=1)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=monday):
|
return_value=monday):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=tuesday):
|
return_value=tuesday):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
@ -366,21 +364,21 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
tuesday = monday + timedelta(days=1)
|
tuesday = monday + timedelta(days=1)
|
||||||
wednesday = tuesday + timedelta(days=1)
|
wednesday = tuesday + timedelta(days=1)
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=monday):
|
return_value=monday):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=tuesday):
|
return_value=tuesday):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(2, len(self.calls))
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
with patch('homeassistant.components.automation.time.dt_util.now',
|
with patch('homeassistant.helpers.condition.dt_util.now',
|
||||||
return_value=wednesday):
|
return_value=wednesday):
|
||||||
self.hass.bus.fire('test_event')
|
self.hass.bus.fire('test_event')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
68
tests/helpers/test_condition.py
Normal file
68
tests/helpers/test_condition.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"""Test the condition helper."""
|
||||||
|
from homeassistant.helpers import condition
|
||||||
|
|
||||||
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
|
|
||||||
|
class TestConditionHelper:
|
||||||
|
"""Test condition helpers."""
|
||||||
|
|
||||||
|
def setup_method(self, method):
|
||||||
|
"""Setup things to be run when tests are started."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
def teardown_method(self, method):
|
||||||
|
"""Stop everything that was started."""
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_and_condition(self):
|
||||||
|
"""Test the 'and' condition."""
|
||||||
|
test = condition.from_config({
|
||||||
|
'condition': 'and',
|
||||||
|
'conditions': [
|
||||||
|
{
|
||||||
|
'condition': 'state',
|
||||||
|
'entity_id': 'sensor.temperature',
|
||||||
|
'state': '100',
|
||||||
|
}, {
|
||||||
|
'condition': 'numeric_state',
|
||||||
|
'entity_id': 'sensor.temperature',
|
||||||
|
'below': 110,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set('sensor.temperature', 120)
|
||||||
|
assert not test(self.hass)
|
||||||
|
|
||||||
|
self.hass.states.set('sensor.temperature', 105)
|
||||||
|
assert not test(self.hass)
|
||||||
|
|
||||||
|
self.hass.states.set('sensor.temperature', 100)
|
||||||
|
assert test(self.hass)
|
||||||
|
|
||||||
|
def test_or_condition(self):
|
||||||
|
"""Test the 'or' condition."""
|
||||||
|
test = condition.from_config({
|
||||||
|
'condition': 'or',
|
||||||
|
'conditions': [
|
||||||
|
{
|
||||||
|
'condition': 'state',
|
||||||
|
'entity_id': 'sensor.temperature',
|
||||||
|
'state': '100',
|
||||||
|
}, {
|
||||||
|
'condition': 'numeric_state',
|
||||||
|
'entity_id': 'sensor.temperature',
|
||||||
|
'below': 110,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set('sensor.temperature', 120)
|
||||||
|
assert not test(self.hass)
|
||||||
|
|
||||||
|
self.hass.states.set('sensor.temperature', 105)
|
||||||
|
assert test(self.hass)
|
||||||
|
|
||||||
|
self.hass.states.set('sensor.temperature', 100)
|
||||||
|
assert test(self.hass)
|
@ -216,3 +216,36 @@ class TestScriptHelper(unittest.TestCase):
|
|||||||
assert not script_obj.is_running
|
assert not script_obj.is_running
|
||||||
assert len(calls) == 2
|
assert len(calls) == 2
|
||||||
assert calls[-1].data['hello'] == 'universe'
|
assert calls[-1].data['hello'] == 'universe'
|
||||||
|
|
||||||
|
def test_condition(self):
|
||||||
|
"""Test if we can use conditions in a script."""
|
||||||
|
event = 'test_event'
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def record_event(event):
|
||||||
|
"""Add recorded event to set."""
|
||||||
|
events.append(event)
|
||||||
|
|
||||||
|
self.hass.bus.listen(event, record_event)
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'hello')
|
||||||
|
|
||||||
|
script_obj = script.Script(self.hass, [
|
||||||
|
{'event': event},
|
||||||
|
{
|
||||||
|
'condition': 'state',
|
||||||
|
'entity_id': 'test.entity',
|
||||||
|
'state': 'hello',
|
||||||
|
},
|
||||||
|
{'event': event},
|
||||||
|
])
|
||||||
|
|
||||||
|
script_obj.run()
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert len(events) == 2
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'goodbye')
|
||||||
|
|
||||||
|
script_obj.run()
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert len(events) == 3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user