Merge pull request #377 from balloob/automation-improvements

Automation improvements
pull/380/head
Paulus Schoutsen 2015-09-15 12:46:13 -07:00
commit 58c3b03b79
17 changed files with 1402 additions and 616 deletions

View File

@ -7,63 +7,67 @@ Allows to setup simple automation rules via the config file.
import logging
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers import config_per_platform
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.components import logbook
DOMAIN = "automation"
DOMAIN = 'automation'
DEPENDENCIES = ["group"]
DEPENDENCIES = ['group']
CONF_ALIAS = "alias"
CONF_SERVICE = "execute_service"
CONF_SERVICE_ENTITY_ID = "service_entity_id"
CONF_SERVICE_DATA = "service_data"
CONF_IF = "if"
CONF_ALIAS = 'alias'
CONF_SERVICE = 'execute_service'
CONF_SERVICE_ENTITY_ID = 'service_entity_id'
CONF_SERVICE_DATA = 'service_data'
CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
CONF_TRIGGER = 'trigger'
CONF_CONDITION_TYPE = 'condition_type'
CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
CONDITION_TYPE_AND = 'and'
CONDITION_TYPE_OR = 'or'
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Sets up automation. """
success = False
config_key = DOMAIN
found = 1
for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER):
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
while config_key in config:
p_config = _migrate_old_config(config[config_key])
found += 1
config_key = "{} {}".format(DOMAIN, found)
if platform is None:
_LOGGER.error("Unknown automation platform specified: %s", p_type)
continue
action = _get_action(hass, p_config)
name = p_config.get(CONF_ALIAS, config_key)
action = _get_action(hass, p_config.get(CONF_ACTION, {}), name)
if action is None:
return
continue
if CONF_IF in p_config:
action = _process_if(hass, config, p_config[CONF_IF], action)
if CONF_CONDITION in p_config or CONF_CONDITION_TYPE in p_config:
action = _process_if(hass, config, p_config, action)
if platform.trigger(hass, p_config, action):
_LOGGER.info(
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
success = True
else:
_LOGGER.error(
"Error setting up rule %s", p_config.get(CONF_ALIAS, ""))
if action is None:
continue
return success
_process_trigger(hass, config, p_config.get(CONF_TRIGGER, []), name,
action)
return True
def _get_action(hass, config):
def _get_action(hass, config, name):
""" Return an action based on a config. """
name = config.get(CONF_ALIAS, 'Unnamed automation')
if CONF_SERVICE not in config:
_LOGGER.error('Error setting up %s, no action specified.',
name)
return
_LOGGER.error('Error setting up %s, no action specified.', name)
return None
def action():
""" Action to be executed. """
@ -71,7 +75,6 @@ def _get_action(hass, config):
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
domain, service = split_entity_id(config[CONF_SERVICE])
service_data = config.get(CONF_SERVICE_DATA, {})
if not isinstance(service_data, dict):
@ -91,26 +94,107 @@ def _get_action(hass, config):
return action
def _process_if(hass, config, if_configs, action):
def _migrate_old_config(config):
""" Migrate old config to new. """
if CONF_PLATFORM not in config:
return config
_LOGGER.warning(
'You are using an old configuration format. Please upgrade: '
'https://home-assistant.io/components/automation.html')
new_conf = {
CONF_TRIGGER: dict(config),
CONF_CONDITION: config.get('if', []),
CONF_ACTION: dict(config),
}
for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'),
('trigger', 'mqtt_payload', 'payload'),
('trigger', 'state_entity_id', 'entity_id'),
('trigger', 'state_before', 'before'),
('trigger', 'state_after', 'after'),
('trigger', 'state_to', 'to'),
('trigger', 'state_from', 'from'),
('trigger', 'state_hours', 'hours'),
('trigger', 'state_minutes', 'minutes'),
('trigger', 'state_seconds', 'seconds')):
if key in new_conf[cat]:
new_conf[cat][new_key] = new_conf[cat].pop(key)
return new_conf
def _process_if(hass, config, p_config, action):
""" Processes if checks. """
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
if_configs = p_config.get(CONF_CONDITION)
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
if use_trigger:
if_configs = p_config[CONF_TRIGGER]
if isinstance(if_configs, dict):
if_configs = [if_configs]
checks = []
for if_config in if_configs:
p_type = if_config.get(CONF_PLATFORM)
if p_type is None:
_LOGGER.error("No platform defined found for if-statement %s",
if_config)
platform = _resolve_platform('if_action', hass, config,
if_config.get(CONF_PLATFORM))
if platform is None:
continue
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
check = platform.if_action(hass, if_config)
if platform is None or not hasattr(platform, 'if_action'):
_LOGGER.error("Unsupported if-statement platform specified: %s",
p_type)
# Invalid conditions are allowed if we base it on trigger
if check is None and not use_trigger:
return None
checks.append(check)
if cond_type == CONDITION_TYPE_AND:
def if_action():
""" AND all conditions. """
if all(check() for check in checks):
action()
else:
def if_action():
""" OR all conditions. """
if any(check() for check in checks):
action()
return if_action
def _process_trigger(hass, config, trigger_configs, name, action):
""" Setup triggers. """
if isinstance(trigger_configs, dict):
trigger_configs = [trigger_configs]
for conf in trigger_configs:
platform = _resolve_platform('trigger', hass, config,
conf.get(CONF_PLATFORM))
if platform is None:
continue
action = platform.if_action(hass, if_config, action)
if platform.trigger(hass, conf, action):
_LOGGER.info("Initialized rule %s", name)
else:
_LOGGER.error("Error setting up rule %s", name)
return action
def _resolve_platform(method, hass, config, platform):
""" Find automation platform. """
if platform is None:
return None
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
if platform is None or not hasattr(platform, method):
_LOGGER.error("Unknown automation platform specified for %s: %s",
method, platform)
return None
return platform

View File

@ -10,8 +10,8 @@ import homeassistant.components.mqtt as mqtt
DEPENDENCIES = ['mqtt']
CONF_TOPIC = 'mqtt_topic'
CONF_PAYLOAD = 'mqtt_payload'
CONF_TOPIC = 'topic'
CONF_PAYLOAD = 'payload'
def trigger(hass, config, action):

View File

@ -9,9 +9,9 @@ import logging
from homeassistant.helpers.event import track_state_change
CONF_ENTITY_ID = "state_entity_id"
CONF_BELOW = "state_below"
CONF_ABOVE = "state_above"
CONF_ENTITY_ID = "entity_id"
CONF_BELOW = "below"
CONF_ABOVE = "above"
_LOGGER = logging.getLogger(__name__)
@ -48,14 +48,14 @@ def trigger(hass, config, action):
return True
def if_action(hass, config, action):
def if_action(hass, config):
""" Wraps 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 action
return None
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
@ -64,16 +64,14 @@ def if_action(hass, config, action):
_LOGGER.error("Missing configuration key."
" One of %s or %s is required",
CONF_BELOW, CONF_ABOVE)
return action
def state_if():
""" Execute action if state matches. """
return None
def if_numeric_state():
""" Test numeric state condition. """
state = hass.states.get(entity_id)
if state is None or _in_range(state.state, above, below):
action()
return state is not None and _in_range(state.state, above, below)
return state_if
return if_numeric_state
def _in_range(value, range_start, range_end):

View File

@ -10,9 +10,9 @@ from homeassistant.helpers.event import track_state_change
from homeassistant.const import MATCH_ALL
CONF_ENTITY_ID = "state_entity_id"
CONF_FROM = "state_from"
CONF_TO = "state_to"
CONF_ENTITY_ID = "entity_id"
CONF_FROM = "from"
CONF_TO = "to"
CONF_STATE = "state"
@ -26,7 +26,7 @@ def trigger(hass, config, action):
return False
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
@ -38,7 +38,7 @@ def trigger(hass, config, action):
return True
def if_action(hass, config, action):
def if_action(hass, config):
""" Wraps action method with state based condition. """
entity_id = config.get(CONF_ENTITY_ID)
state = config.get(CONF_STATE)
@ -47,11 +47,12 @@ def if_action(hass, config, action):
logging.getLogger(__name__).error(
"Missing if-condition configuration key %s or %s", CONF_ENTITY_ID,
CONF_STATE)
return action
return None
def state_if():
""" Execute action if state matches. """
if hass.states.is_state(entity_id, state):
action()
state = str(state)
return state_if
def if_state():
""" Test if condition. """
return hass.states.is_state(entity_id, state)
return if_state

View File

@ -0,0 +1,103 @@
"""
homeassistant.components.automation.sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers sun based automation rules.
"""
import logging
from datetime import timedelta
from homeassistant.components import sun
from homeassistant.helpers.event import track_point_in_utc_time
import homeassistant.util.dt as dt_util
DEPENDENCIES = ['sun']
CONF_OFFSET = 'offset'
CONF_EVENT = 'event'
EVENT_SUNSET = 'sunset'
EVENT_SUNRISE = 'sunrise'
_LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
""" Listen for events based on config. """
event = config.get(CONF_EVENT)
if event is None:
_LOGGER.error("Missing configuration key %s", CONF_EVENT)
return False
event = event.lower()
if event not in (EVENT_SUNRISE, EVENT_SUNSET):
_LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event)
return False
if CONF_OFFSET in config:
raw_offset = config.get(CONF_OFFSET)
negative_offset = False
if raw_offset.startswith('-'):
negative_offset = True
raw_offset = raw_offset[1:]
try:
(hour, minute, second) = [int(x) for x in raw_offset.split(':')]
except ValueError:
_LOGGER.error('Could not parse offset %s', raw_offset)
return False
offset = timedelta(hours=hour, minutes=minute, seconds=second)
if negative_offset:
offset *= -1
else:
offset = timedelta(0)
# Do something to call action
if event == EVENT_SUNRISE:
trigger_sunrise(hass, action, offset)
else:
trigger_sunset(hass, action, offset)
return True
def trigger_sunrise(hass, action, offset):
""" Trigger action at next sun rise. """
def next_rise():
""" Returns next sunrise. """
next_time = sun.next_rising_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunrise_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
action()
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
def trigger_sunset(hass, action, offset):
""" Trigger action at next sun set. """
def next_set():
""" Returns next sunrise. """
next_time = sun.next_setting_utc(hass) + offset
while next_time < dt_util.utcnow():
next_time = next_time + timedelta(days=1)
return next_time
def sunset_automation_listener(now):
""" Called when it's time for action. """
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
action()
track_point_in_utc_time(hass, sunset_automation_listener, next_set())

View File

@ -10,9 +10,9 @@ from homeassistant.util import convert
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_time_change
CONF_HOURS = "time_hours"
CONF_MINUTES = "time_minutes"
CONF_SECONDS = "time_seconds"
CONF_HOURS = "hours"
CONF_MINUTES = "minutes"
CONF_SECONDS = "seconds"
CONF_BEFORE = "before"
CONF_AFTER = "after"
CONF_WEEKDAY = "weekday"
@ -22,6 +22,14 @@ WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
if CONF_AFTER in config:
after = dt_util.parse_time_str(config[CONF_AFTER])
if after is None:
logging.getLogger(__name__).error(
'Received invalid after value: %s', config[CONF_AFTER])
return False
hours, minutes, seconds = after.hour, after.minute, after.second
hours = convert(config.get(CONF_HOURS), int)
minutes = convert(config.get(CONF_MINUTES), int)
seconds = convert(config.get(CONF_SECONDS), int)
@ -36,7 +44,7 @@ def trigger(hass, config, action):
return True
def if_action(hass, config, action):
def if_action(hass, config):
""" Wraps action method with time based condition. """
before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER)
@ -46,37 +54,38 @@ def if_action(hass, config, action):
logging.getLogger(__name__).error(
"Missing if-condition configuration key %s, %s or %s",
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
return None
def time_if():
""" Validate time based if-condition """
now = dt_util.now()
if before is not None:
# Strip seconds if given
before_h, before_m = before.split(':')[0:2]
time = dt_util.parse_time_str(before)
if time is None:
return False
before_point = now.replace(hour=int(before_h),
minute=int(before_m))
before_point = now.replace(hour=time.hour, minute=time.minute)
if now > before_point:
return
return False
if after is not None:
# Strip seconds if given
after_h, after_m = after.split(':')[0:2]
time = dt_util.parse_time_str(after)
if time is None:
return False
after_point = now.replace(hour=int(after_h), minute=int(after_m))
after_point = now.replace(hour=time.hour, minute=time.minute)
if now < after_point:
return
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
return False
action()
return True
return time_if

View File

@ -1,137 +0,0 @@
"""
homeassistant.components.scheduler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A component that will act as a scheduler and perform actions based
on the events in the schedule.
It will read a json object from schedule.json in the config dir
and create a schedule based on it.
Each schedule is a JSON with the keys id, name, description,
entity_ids, and events.
- days is an array with the weekday number (monday=0) that the schedule
is active
- entity_ids an array with entity ids that the events in the schedule should
effect (can also be groups)
- events is an array of objects that describe the different events that is
supported. Read in the events descriptions for more information.
"""
import logging
import json
from homeassistant import bootstrap
from homeassistant.loader import get_component
from homeassistant.const import ATTR_ENTITY_ID
DOMAIN = 'scheduler'
DEPENDENCIES = []
_LOGGER = logging.getLogger(__name__)
_SCHEDULE_FILE = 'schedule.json'
def setup(hass, config):
""" Create the schedules. """
def setup_listener(schedule, event_data):
""" Creates the event listener based on event_data. """
event_type = event_data['type']
component = event_type
# if the event isn't part of a component
if event_type in ['time']:
component = 'scheduler.{}'.format(event_type)
elif not bootstrap.setup_component(hass, component, config):
_LOGGER.warn("Could setup event listener for %s", component)
return None
return get_component(component).create_event_listener(schedule,
event_data)
def setup_schedule(schedule_data):
""" Setup a schedule based on the description. """
schedule = Schedule(schedule_data['id'],
name=schedule_data['name'],
description=schedule_data['description'],
entity_ids=schedule_data['entity_ids'],
days=schedule_data['days'])
for event_data in schedule_data['events']:
event_listener = setup_listener(schedule, event_data)
if event_listener:
schedule.add_event_listener(event_listener)
schedule.schedule(hass)
return True
with open(hass.config.path(_SCHEDULE_FILE)) as schedule_file:
schedule_descriptions = json.load(schedule_file)
for schedule_description in schedule_descriptions:
if not setup_schedule(schedule_description):
return False
return True
class Schedule(object):
""" A Schedule """
# pylint: disable=too-many-arguments
def __init__(self, schedule_id, name=None, description=None,
entity_ids=None, days=None):
self.schedule_id = schedule_id
self.name = name
self.description = description
self.entity_ids = entity_ids or []
self.days = days or [0, 1, 2, 3, 4, 5, 6]
self.__event_listeners = []
def add_event_listener(self, event_listener):
""" Add a event to the schedule. """
self.__event_listeners.append(event_listener)
def schedule(self, hass):
""" Schedule all the events in the schedule. """
for event in self.__event_listeners:
event.schedule(hass)
class EventListener(object):
""" The base EventListener class that the schedule uses. """
def __init__(self, schedule):
self.my_schedule = schedule
def schedule(self, hass):
""" Schedule the event """
pass
def execute(self, hass):
""" execute the event """
pass
# pylint: disable=too-few-public-methods
class ServiceEventListener(EventListener):
""" A EventListener that calls a service when executed. """
def __init__(self, schdule, service):
EventListener.__init__(self, schdule)
(self.domain, self.service) = service.split('.')
def execute(self, hass):
""" Call the service. """
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
hass.services.call(self.domain, self.service, data)
# Reschedule for next day
self.schedule(hass)

View File

@ -1,70 +0,0 @@
"""
homeassistant.components.scheduler.time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An event in the scheduler component that will call the service
every specified day at the time specified.
A time event need to have the type 'time', which service to call and at
which time.
{
"type": "time",
"service": "switch.turn_off",
"time": "22:00:00"
}
"""
from datetime import timedelta
import logging
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components.scheduler import ServiceEventListener
_LOGGER = logging.getLogger(__name__)
def create_event_listener(schedule, event_listener_data):
""" Create a TimeEvent based on the description. """
service = event_listener_data['service']
(hour, minute, second) = [int(x) for x in
event_listener_data['time'].split(':', 3)]
return TimeEventListener(schedule, service, hour, minute, second)
# pylint: disable=too-few-public-methods
class TimeEventListener(ServiceEventListener):
""" The time event that the scheduler uses. """
# pylint: disable=too-many-arguments
def __init__(self, schedule, service, hour, minute, second):
ServiceEventListener.__init__(self, schedule, service)
self.hour = hour
self.minute = minute
self.second = second
def schedule(self, hass):
""" Schedule this event so that it will be called. """
next_time = dt_util.now().replace(
hour=self.hour, minute=self.minute, second=self.second)
# Calculate the next time the event should be executed.
# That is the next day that the schedule is configured to run
while next_time < dt_util.now() or \
next_time.weekday() not in self.my_schedule.days:
next_time = next_time + timedelta(days=1)
# pylint: disable=unused-argument
def execute(now):
""" Call the execute method """
self.execute(hass)
track_point_in_time(hass, execute, next_time)
_LOGGER.info(
'TimeEventListener scheduled for %s, will call service %s.%s',
next_time, self.domain, self.service)

View File

@ -25,10 +25,8 @@ import urllib
import homeassistant.util as util
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import (
track_point_in_utc_time, track_point_in_time)
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.helpers.entity import Entity
from homeassistant.components.scheduler import ServiceEventListener
DEPENDENCIES = []
REQUIREMENTS = ['astral==0.8.1']
@ -214,95 +212,3 @@ class Sun(Entity):
track_point_in_utc_time(
self.hass, self.point_in_time_listener,
self.next_change + timedelta(seconds=1))
def create_event_listener(schedule, event_listener_data):
""" Create a sun event listener based on the description. """
negative_offset = False
service = event_listener_data['service']
offset_str = event_listener_data['offset']
event = event_listener_data['event']
if offset_str.startswith('-'):
negative_offset = True
offset_str = offset_str[1:]
(hour, minute, second) = [int(x) for x in offset_str.split(':')]
offset = timedelta(hours=hour, minutes=minute, seconds=second)
if event == 'sunset':
return SunsetEventListener(schedule, service, offset, negative_offset)
return SunriseEventListener(schedule, service, offset, negative_offset)
# pylint: disable=too-few-public-methods
class SunEventListener(ServiceEventListener):
""" This is the base class for sun event listeners. """
def __init__(self, schedule, service, offset, negative_offset):
ServiceEventListener.__init__(self, schedule, service)
self.offset = offset
self.negative_offset = negative_offset
def __get_next_time(self, next_event):
"""
Returns when the next time the service should be called.
Taking into account the offset and which days the event should execute.
"""
if self.negative_offset:
next_time = next_event - self.offset
else:
next_time = next_event + self.offset
while next_time < dt_util.now() or \
next_time.weekday() not in self.my_schedule.days:
next_time = next_time + timedelta(days=1)
return next_time
def schedule_next_event(self, hass, next_event):
""" Schedule the event. """
next_time = self.__get_next_time(next_event)
# pylint: disable=unused-argument
def execute(now):
""" Call the execute method. """
self.execute(hass)
track_point_in_time(hass, execute, next_time)
return next_time
# pylint: disable=too-few-public-methods
class SunsetEventListener(SunEventListener):
""" This class is used the call a service when the sun sets. """
def schedule(self, hass):
""" Schedule the event """
next_setting_dt = next_setting(hass)
next_time_dt = self.schedule_next_event(hass, next_setting_dt)
_LOGGER.info(
'SunsetEventListener scheduled for %s, will call service %s.%s',
next_time_dt, self.domain, self.service)
# pylint: disable=too-few-public-methods
class SunriseEventListener(SunEventListener):
""" This class is used the call a service when the sun rises. """
def schedule(self, hass):
""" Schedule the event. """
next_rising_dt = next_rising(hass)
next_time_dt = self.schedule_next_event(hass, next_rising_dt)
_LOGGER.info(
'SunriseEventListener scheduled for %s, will call service %s.%s',
next_time_dt, self.domain, self.service)

View File

@ -131,3 +131,20 @@ def date_str_to_date(dt_str):
def strip_microseconds(dattim):
""" Returns a copy of dattime object but with microsecond set to 0. """
return dattim.replace(microsecond=0)
def parse_time_str(time_str):
""" Parse a time string (00:20:00) into Time object.
Return None if invalid.
"""
parts = str(time_str).split(':')
if len(parts) < 2:
return None
try:
hour = int(parts[0])
minute = int(parts[1])
second = int(parts[2]) if len(parts) > 2 else 0
return dt.time(hour, minute, second)
except ValueError:
# ValueError if value cannot be converted to an int or not in range
return None

View File

@ -1,15 +1,13 @@
"""
tests.test_component_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~
tests.components.automation.test_event
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
Tests event automation.
"""
import unittest
import homeassistant.core as ha
import homeassistant.components.automation as automation
import homeassistant.components.automation.event as event
from homeassistant.const import CONF_PLATFORM
class TestAutomationEvent(unittest.TestCase):
@ -28,20 +26,57 @@ class TestAutomationEvent(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_fails_setup_if_no_event_type(self):
self.assertFalse(automation.setup(self.hass, {
def test_old_config_if_fires_on_event(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
automation.CONF_SERVICE: 'test.automation'
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation'
}
}))
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_on_event_with_data(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'event_data': {'some_attr': 'some_value'},
'execute_service': 'test.automation'
}
}))
self.hass.bus.fire('test_event', {'some_attr': 'some_value'})
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_not_fires_if_event_data_not_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'event_data': {'some_attr': 'some_value'},
'execute_service': 'test.automation'
}
}))
self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'})
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_event(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'execute_service': 'test.automation',
}
}
}))
@ -52,10 +87,14 @@ class TestAutomationEvent(unittest.TestCase):
def test_if_fires_on_event_with_data(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
event.CONF_EVENT_DATA: {'some_attr': 'some_value'},
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'event',
'event_type': 'test_event',
'event_data': {'some_attr': 'some_value'}
},
'action': {
'execute_service': 'test.automation',
}
}
}))
@ -66,10 +105,14 @@ class TestAutomationEvent(unittest.TestCase):
def test_if_not_fires_if_event_data_not_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
event.CONF_EVENT_DATA: {'some_attr': 'some_value'},
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'event',
'event_type': 'test_event',
'event_data': {'some_attr': 'some_value'}
},
'action': {
'execute_service': 'test.automation',
}
}
}))

View File

@ -1,15 +1,13 @@
"""
tests.test_component_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
tests.components.automation.test_init
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests automation component.
"""
import unittest
import homeassistant.core as ha
import homeassistant.components.automation as automation
import homeassistant.components.automation.event as event
from homeassistant.const import CONF_PLATFORM, ATTR_ENTITY_ID
from homeassistant.const import ATTR_ENTITY_ID
class TestAutomationEvent(unittest.TestCase):
@ -28,20 +26,13 @@ class TestAutomationEvent(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_setup_fails_if_unknown_platform(self):
self.assertFalse(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'i_do_not_exist'
}
}))
def test_service_data_not_a_dict(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_SERVICE_DATA: 100
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_data': 100
}
})
@ -49,13 +40,64 @@ class TestAutomationEvent(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_service_specify_data(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_data': {'some': 'data'}
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual('data', self.calls[0].data['some'])
def test_old_config_service_specify_entity_id(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_entity_id': 'hello.world'
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_old_config_service_specify_entity_id_list(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'service_entity_id': ['hello.world', 'hello.world2']
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world', 'hello.world2'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_service_specify_data(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_SERVICE_DATA: {'some': 'data'}
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'execute_service': 'test.automation',
'service_data': {'some': 'data'}
}
}
})
@ -67,29 +109,216 @@ class TestAutomationEvent(unittest.TestCase):
def test_service_specify_entity_id(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_SERVICE_ENTITY_ID: 'hello.world'
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'execute_service': 'test.automation',
'service_entity_id': 'hello.world'
}
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world'], self.calls[0].data[ATTR_ENTITY_ID])
self.assertEqual(['hello.world'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_service_specify_entity_id_list(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_SERVICE_ENTITY_ID: ['hello.world', 'hello.world2']
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'execute_service': 'test.automation',
'service_entity_id': ['hello.world', 'hello.world2']
}
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world', 'hello.world2'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_two_triggers(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': [
{
'platform': 'event',
'event_type': 'test_event',
},
{
'platform': 'state',
'entity_id': 'test.entity',
}
],
'action': {
'execute_service': 'test.automation',
}
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set('test.entity', 'hello')
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))
def test_two_conditions_with_and(self):
entity_id = 'test.entity'
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': [
{
'platform': 'event',
'event_type': 'test_event',
},
],
'condition': [
{
'platform': 'state',
'entity_id': entity_id,
'state': 100
},
{
'platform': 'numeric_state',
'entity_id': entity_id,
'below': 150
}
],
'action': {
'execute_service': 'test.automation',
}
}
})
self.hass.states.set(entity_id, 100)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, 101)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, 151)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_two_conditions_with_or(self):
entity_id = 'test.entity'
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': [
{
'platform': 'event',
'event_type': 'test_event',
},
],
'condition_type': 'OR',
'condition': [
{
'platform': 'state',
'entity_id': entity_id,
'state': 200
},
{
'platform': 'numeric_state',
'entity_id': entity_id,
'below': 150
}
],
'action': {
'execute_service': 'test.automation',
}
}
})
self.hass.states.set(entity_id, 200)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, 100)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))
self.hass.states.set(entity_id, 250)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))
def test_using_trigger_as_condition(self):
""" """
entity_id = 'test.entity'
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': [
{
'platform': 'state',
'entity_id': entity_id,
'state': 100
},
{
'platform': 'numeric_state',
'entity_id': entity_id,
'below': 150
}
],
'condition': 'use_trigger_values',
'action': {
'execute_service': 'test.automation',
}
}
})
self.hass.states.set(entity_id, 100)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, 120)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, 151)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_using_trigger_as_condition_with_invalid_condition(self):
""" Event is not a valid condition. Will it still work? """
entity_id = 'test.entity'
self.hass.states.set(entity_id, 100)
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': [
{
'platform': 'event',
'event_type': 'test_event',
},
{
'platform': 'numeric_state',
'entity_id': entity_id,
'below': 150
}
],
'condition': 'use_trigger_values',
'action': {
'execute_service': 'test.automation',
}
}
})
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world', 'hello.world2'], self.calls[0].data[ATTR_ENTITY_ID])

View File

@ -1,16 +1,13 @@
"""
tests.test_component_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~
tests.components.automation.test_mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
Tests mqtt automation.
"""
import unittest
import homeassistant.core as ha
import homeassistant.components.automation as automation
import homeassistant.components.automation.mqtt as mqtt
from homeassistant.const import CONF_PLATFORM
from tests.common import mock_mqtt_component, fire_mqtt_message
@ -31,20 +28,57 @@ class TestAutomationState(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_setup_fails_if_no_topic(self):
self.assertFalse(automation.setup(self.hass, {
def test_old_config_if_fires_on_topic_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'mqtt',
automation.CONF_SERVICE: 'test.automation'
'platform': 'mqtt',
'mqtt_topic': 'test-topic',
'execute_service': 'test.automation'
}
}))
fire_mqtt_message(self.hass, 'test-topic', '')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_fires_on_topic_and_payload_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'mqtt',
'mqtt_topic': 'test-topic',
'mqtt_payload': 'hello',
'execute_service': 'test.automation'
}
}))
fire_mqtt_message(self.hass, 'test-topic', 'hello')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_old_config_if_not_fires_on_topic_but_no_payload_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'platform': 'mqtt',
'mqtt_topic': 'test-topic',
'mqtt_payload': 'hello',
'execute_service': 'test.automation'
}
}))
fire_mqtt_message(self.hass, 'test-topic', 'no-hello')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_topic_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'mqtt',
mqtt.CONF_TOPIC: 'test-topic',
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'mqtt',
'topic': 'test-topic'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -55,10 +89,14 @@ class TestAutomationState(unittest.TestCase):
def test_if_fires_on_topic_and_payload_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'mqtt',
mqtt.CONF_TOPIC: 'test-topic',
mqtt.CONF_PAYLOAD: 'hello',
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'mqtt',
'topic': 'test-topic',
'payload': 'hello'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -69,10 +107,14 @@ class TestAutomationState(unittest.TestCase):
def test_if_not_fires_on_topic_but_no_payload_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'mqtt',
mqtt.CONF_TOPIC: 'test-topic',
mqtt.CONF_PAYLOAD: 'hello',
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'mqtt',
'topic': 'test-topic',
'payload': 'hello'
},
'action': {
'execute_service': 'test.automation'
}
}
}))

View File

@ -1,15 +1,13 @@
"""
tests.test_component_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~
tests.components.automation.test_numeric_state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
Tests numeric state automation.
"""
import unittest
import homeassistant.core as ha
import homeassistant.components.automation as automation
from homeassistant.components.automation import event, numeric_state
from homeassistant.const import CONF_PLATFORM
class TestAutomationNumericState(unittest.TestCase):
@ -28,31 +26,17 @@ class TestAutomationNumericState(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_setup_fails_if_no_entity_id(self):
self.assertFalse(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
def test_setup_fails_if_no_condition(self):
self.assertFalse(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
automation.CONF_SERVICE: 'test.automation'
}
}))
def test_if_fires_on_entity_change_below(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'below': 10,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
# 9 is below 10
@ -66,10 +50,14 @@ class TestAutomationNumericState(unittest.TestCase):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'below': 10,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -78,17 +66,20 @@ class TestAutomationNumericState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_not_fires_on_entity_change_below_to_below(self):
self.hass.states.set('test.entity', 9)
self.hass.pool.block_till_done()
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'below': 10,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -97,14 +88,17 @@ class TestAutomationNumericState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_entity_change_above(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'above': 10,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
# 11 is above 10
@ -119,10 +113,14 @@ class TestAutomationNumericState(unittest.TestCase):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'above': 10,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -131,7 +129,6 @@ class TestAutomationNumericState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_not_fires_on_entity_change_above_to_above(self):
# set initial state
self.hass.states.set('test.entity', 11)
@ -139,10 +136,14 @@ class TestAutomationNumericState(unittest.TestCase):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'above': 10,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -154,11 +155,15 @@ class TestAutomationNumericState(unittest.TestCase):
def test_if_fires_on_entity_change_below_range(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'below': 10,
'above': 5,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
# 9 is below 10
@ -169,11 +174,15 @@ class TestAutomationNumericState(unittest.TestCase):
def test_if_fires_on_entity_change_below_above_range(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'below': 10,
'above': 5,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
# 4 is below 5
@ -187,11 +196,15 @@ class TestAutomationNumericState(unittest.TestCase):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'below': 10,
'above': 5,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -206,11 +219,15 @@ class TestAutomationNumericState(unittest.TestCase):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.entity',
'below': 10,
'above': 5,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -222,10 +239,13 @@ class TestAutomationNumericState(unittest.TestCase):
def test_if_not_fires_if_entity_not_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.another_entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
'trigger': {
'platform': 'numeric_state',
'entity_id': 'test.another_entity',
},
'action': {
'execute_service': 'test.automation'
}
}
}))
@ -238,19 +258,23 @@ class TestAutomationNumericState(unittest.TestCase):
test_state = 10
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_IF: [{
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: entity_id,
numeric_state.CONF_ABOVE: test_state,
numeric_state.CONF_BELOW: test_state + 2,
}]
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'condition': {
'platform': 'numeric_state',
'entity_id': entity_id,
'above': test_state,
'below': test_state + 2
},
'action': {
'execute_service': 'test.automation'
}
}
})
self.hass.states.set(entity_id, test_state )
self.hass.states.set(entity_id, test_state)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()

View File

@ -1,15 +1,13 @@
"""
tests.test_component_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~
tests.components.automation.test_state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
Tests state automation.
"""
import unittest
import homeassistant.core as ha
import homeassistant.components.automation as automation
from homeassistant.components.automation import event, state
from homeassistant.const import CONF_PLATFORM
class TestAutomationState(unittest.TestCase):
@ -29,20 +27,12 @@ class TestAutomationState(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_setup_fails_if_no_entity_id(self):
self.assertFalse(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
automation.CONF_SERVICE: 'test.automation'
}
}))
def test_if_fires_on_entity_change(self):
def test_old_config_if_fires_on_entity_change(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: 'test.entity',
automation.CONF_SERVICE: 'test.automation'
'platform': 'state',
'state_entity_id': 'test.entity',
'execute_service': 'test.automation'
}
}))
@ -50,13 +40,13 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_with_from_filter(self):
def test_old_config_if_fires_on_entity_change_with_from_filter(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: 'test.entity',
state.CONF_FROM: 'hello',
automation.CONF_SERVICE: 'test.automation'
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'execute_service': 'test.automation'
}
}))
@ -64,13 +54,13 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_with_to_filter(self):
def test_old_config_if_fires_on_entity_change_with_to_filter(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: 'test.entity',
state.CONF_TO: 'world',
automation.CONF_SERVICE: 'test.automation'
'platform': 'state',
'state_entity_id': 'test.entity',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
@ -78,14 +68,14 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_with_both_filters(self):
def test_old_config_if_fires_on_entity_change_with_both_filters(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: 'test.entity',
state.CONF_FROM: 'hello',
state.CONF_TO: 'world',
automation.CONF_SERVICE: 'test.automation'
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
@ -93,14 +83,14 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_not_fires_if_to_filter_not_match(self):
def test_old_config_if_not_fires_if_to_filter_not_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: 'test.entity',
state.CONF_FROM: 'hello',
state.CONF_TO: 'world',
automation.CONF_SERVICE: 'test.automation'
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
@ -108,16 +98,16 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_not_fires_if_from_filter_not_match(self):
def test_old_config_if_not_fires_if_from_filter_not_match(self):
self.hass.states.set('test.entity', 'bye')
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: 'test.entity',
state.CONF_FROM: 'hello',
state.CONF_TO: 'world',
automation.CONF_SERVICE: 'test.automation'
'platform': 'state',
'state_entity_id': 'test.entity',
'state_from': 'hello',
'state_to': 'world',
'execute_service': 'test.automation'
}
}))
@ -125,12 +115,12 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_not_fires_if_entity_not_match(self):
def test_old_config_if_not_fires_if_entity_not_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: 'test.another_entity',
automation.CONF_SERVICE: 'test.automation'
'platform': 'state',
'state_entity_id': 'test.another_entity',
'execute_service': 'test.automation'
}
}))
@ -138,18 +128,18 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_action(self):
def test_old_config_if_action(self):
entity_id = 'domain.test_entity'
test_state = 'new_state'
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_IF: [{
CONF_PLATFORM: 'state',
state.CONF_ENTITY_ID: entity_id,
state.CONF_STATE: test_state,
'platform': 'event',
'event_type': 'test_event',
'execute_service': 'test.automation',
'if': [{
'platform': 'state',
'entity_id': entity_id,
'state': test_state,
}]
}
})
@ -165,3 +155,182 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.entity',
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_with_from_filter(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.entity',
'from': 'hello'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_with_to_filter(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.entity',
'to': 'world'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_with_state_filter(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.entity',
'state': 'world'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_with_both_filters(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.entity',
'from': 'hello',
'to': 'world'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_not_fires_if_to_filter_not_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.entity',
'from': 'hello',
'to': 'world'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'moon')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_not_fires_if_from_filter_not_match(self):
self.hass.states.set('test.entity', 'bye')
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.entity',
'from': 'hello',
'to': 'world'
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_not_fires_if_entity_not_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'state',
'entity_id': 'test.anoter_entity',
},
'action': {
'execute_service': 'test.automation'
}
}
}))
self.hass.states.set('test.entity', 'world')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_action(self):
entity_id = 'domain.test_entity'
test_state = 'new_state'
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'condition': [{
'platform': 'state',
'entity_id': entity_id,
'state': test_state
}],
'action': {
'execute_service': 'test.automation'
}
}
})
self.hass.states.set(entity_id, test_state)
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, test_state + 'something')
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))

View File

@ -0,0 +1,128 @@
"""
tests.components.automation.test_sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests sun automation.
"""
from datetime import datetime
import unittest
import homeassistant.core as ha
from homeassistant.components import sun
import homeassistant.components.automation as automation
import homeassistant.util.dt as dt_util
from tests.common import fire_time_changed
class TestAutomationSun(unittest.TestCase):
""" Test the sun automation. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
self.hass.config.components.append('sun')
self.calls = []
def record_call(service):
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_sunset_trigger(self):
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015',
})
trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC)
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'sun',
'event': 'sunset',
},
'action': {
'execute_service': 'test.automation',
}
}
}))
fire_time_changed(self.hass, trigger_time)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_sunrise_trigger(self):
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
})
trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC)
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'sun',
'event': 'sunrise',
},
'action': {
'execute_service': 'test.automation',
}
}
}))
fire_time_changed(self.hass, trigger_time)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_sunset_trigger_with_offset(self):
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015',
})
trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC)
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'sun',
'event': 'sunset',
'offset': '0:30:00'
},
'action': {
'execute_service': 'test.automation',
}
}
}))
fire_time_changed(self.hass, trigger_time)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_sunrise_trigger_with_offset(self):
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, {
sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015',
})
trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC)
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'sun',
'event': 'sunrise',
'offset': '-0:30:00'
},
'action': {
'execute_service': 'test.automation',
}
}
}))
fire_time_changed(self.hass, trigger_time)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))

View File

@ -1,8 +1,8 @@
"""
tests.test_component_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~
tests.components.automation.test_time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
Tests time automation.
"""
from datetime import timedelta
import unittest
@ -33,7 +33,7 @@ class TestAutomationTime(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_if_fires_when_hour_matches(self):
def test_old_config_if_fires_when_hour_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'time',
@ -48,7 +48,7 @@ class TestAutomationTime(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_when_minute_matches(self):
def test_old_config_if_fires_when_minute_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'time',
@ -63,7 +63,7 @@ class TestAutomationTime(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_when_second_matches(self):
def test_old_config_if_fires_when_second_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'time',
@ -78,7 +78,7 @@ class TestAutomationTime(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_when_all_matches(self):
def test_old_config_if_fires_when_all_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'time',
@ -96,13 +96,13 @@ class TestAutomationTime(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_action_before(self):
def test_old_config_if_action_before(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_IF: {
'if': {
CONF_PLATFORM: 'time',
time.CONF_BEFORE: '10:00'
}
@ -126,13 +126,13 @@ class TestAutomationTime(unittest.TestCase):
self.assertEqual(1, len(self.calls))
def test_if_action_after(self):
def test_old_config_if_action_after(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_IF: {
'if': {
CONF_PLATFORM: 'time',
time.CONF_AFTER: '10:00'
}
@ -156,13 +156,13 @@ class TestAutomationTime(unittest.TestCase):
self.assertEqual(1, len(self.calls))
def test_if_action_one_weekday(self):
def test_old_config_if_action_one_weekday(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_IF: {
'if': {
CONF_PLATFORM: 'time',
time.CONF_WEEKDAY: 'mon',
}
@ -187,13 +187,13 @@ class TestAutomationTime(unittest.TestCase):
self.assertEqual(1, len(self.calls))
def test_if_action_list_weekday(self):
def test_old_config_if_action_list_weekday(self):
automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'event',
event.CONF_EVENT_TYPE: 'test_event',
automation.CONF_SERVICE: 'test.automation',
automation.CONF_IF: {
'if': {
CONF_PLATFORM: 'time',
time.CONF_WEEKDAY: ['mon', 'tue'],
}
@ -225,3 +225,243 @@ class TestAutomationTime(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))
def test_if_fires_when_hour_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'time',
'hours': 0,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0))
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_when_minute_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'time',
'minutes': 0,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0))
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_when_second_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'time',
'seconds': 0,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(second=0))
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_when_all_matches(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'time',
'hours': 0,
'minutes': 0,
'seconds': 0,
},
'action': {
'execute_service': 'test.automation'
}
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(
hour=0, minute=0, second=0))
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_using_after(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'time',
'after': '5:00:00',
},
'action': {
'execute_service': 'test.automation'
}
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(
hour=5, minute=0, second=0))
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_action_before(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event'
},
'condition': {
'platform': 'time',
'before': '10:00',
},
'action': {
'execute_service': 'test.automation'
}
}
})
before_10 = dt_util.now().replace(hour=8)
after_10 = dt_util.now().replace(hour=14)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=before_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=after_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_action_after(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event'
},
'condition': {
'platform': 'time',
'after': '10:00',
},
'action': {
'execute_service': 'test.automation'
}
}
})
before_10 = dt_util.now().replace(hour=8)
after_10 = dt_util.now().replace(hour=14)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=before_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=after_10):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_action_one_weekday(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event'
},
'condition': {
'platform': 'time',
'weekday': 'mon',
},
'action': {
'execute_service': 'test.automation'
}
}
})
days_past_monday = dt_util.now().weekday()
monday = dt_util.now() - timedelta(days=days_past_monday)
tuesday = monday + timedelta(days=1)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=monday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=tuesday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_action_list_weekday(self):
automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event'
},
'condition': {
'platform': 'time',
'weekday': ['mon', 'tue'],
},
'action': {
'execute_service': 'test.automation'
}
}
})
days_past_monday = dt_util.now().weekday()
monday = dt_util.now() - timedelta(days=days_past_monday)
tuesday = monday + timedelta(days=1)
wednesday = tuesday + timedelta(days=1)
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=monday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=tuesday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))
with patch('homeassistant.components.automation.time.dt_util.now',
return_value=wednesday):
self.hass.bus.fire('test_event')
self.hass.pool.block_till_done()
self.assertEqual(2, len(self.calls))