diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index a89afeb8c21..45859617624 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -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 diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 7004b919c72..3f85792f907 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -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): diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 417ffffff7d..7e014213d62 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -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): diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index d336fcaa3d7..8baa0a01d46 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -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 diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py new file mode 100644 index 00000000000..103df6c9b39 --- /dev/null +++ b/homeassistant/components/automation/sun.py @@ -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()) diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index b97f3e2f7f5..821295fdffa 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -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 diff --git a/homeassistant/components/scheduler/__init__.py b/homeassistant/components/scheduler/__init__.py deleted file mode 100644 index 1a67636da3d..00000000000 --- a/homeassistant/components/scheduler/__init__.py +++ /dev/null @@ -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) diff --git a/homeassistant/components/scheduler/time.py b/homeassistant/components/scheduler/time.py deleted file mode 100644 index 4d0280dfdf9..00000000000 --- a/homeassistant/components/scheduler/time.py +++ /dev/null @@ -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) diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index 802eddb4a3a..ce4dbd1e937 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -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) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index d8fecf20db8..35795a7ae7f 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -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 diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index a2c36283c9a..01867f3850e 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -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', + } } })) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 507c37dc20a..6a011a072a5 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -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]) diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 9402b5300b6..d5e969abe5d 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -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' + } } })) diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 19a0f183876..e946c138a95 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -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() diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 9dcfa49d54c..b0410c75014 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -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)) diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py new file mode 100644 index 00000000000..dcb9cbafc1f --- /dev/null +++ b/tests/components/automation/test_sun.py @@ -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)) diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 48544bca25a..f7187592c66 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -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))