diff --git a/config/home-assistant.conf.example b/config/home-assistant.conf.example index 0dc87732864..0885a1d360f 100644 --- a/config/home-assistant.conf.example +++ b/config/home-assistant.conf.example @@ -79,3 +79,26 @@ unknown_light=group.living_room [browser] [keyboard] + +[automation] +platform=state +alias=Sun starts shining + +state_entity_id=sun.sun +# Next two are optional, omit to match all +state_from=below_horizon +state_to=above_horizon + +execute_service=light.turn_off +service_entity_id=group.living_room + +[automation 2] +platform=time +alias=Beer o Clock + +time_hours=16 +time_minutes=0 +time_seconds=0 + +execute_service=notify.notify +execute_service_data={"message":"It's 4, time for beer!"} diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 0ed4b2b2fd2..9cf8144b45a 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -220,10 +220,10 @@ def _process_match_param(parameter): """ Wraps parameter in a list if it is not one and returns it. """ if parameter is None or parameter == MATCH_ALL: return MATCH_ALL - elif isinstance(parameter, list): - return parameter - else: + elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'): return (parameter,) + else: + return tuple(parameter) def _matcher(subject, pattern): @@ -592,7 +592,7 @@ class StateMachine(object): if isinstance(entity_ids, str): entity_ids = (entity_ids.lower(),) else: - entity_ids = [entity_id.lower() for entity_id in entity_ids] + entity_ids = tuple(entity_id.lower() for entity_id in entity_ids) @ft.wraps(action) def state_listener(event): diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py new file mode 100644 index 00000000000..33eef9fe3dc --- /dev/null +++ b/homeassistant/components/automation/__init__.py @@ -0,0 +1,71 @@ +""" +homeassistant.components.automation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Allows to setup simple automation rules via the config file. +""" +import logging +import json + +from homeassistant.loader import get_component +from homeassistant.helpers import config_per_platform +from homeassistant.util import convert, split_entity_id +from homeassistant.const import ATTR_ENTITY_ID + +DOMAIN = "automation" + +DEPENDENCIES = ["group"] + +CONF_ALIAS = "alias" +CONF_SERVICE = "execute_service" +CONF_SERVICE_ENTITY_ID = "service_entity_id" +CONF_SERVICE_DATA = "service_data" + +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Sets up automation. """ + + for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER): + platform = get_component('automation.{}'.format(p_type)) + + if platform is None: + _LOGGER.error("Unknown automation platform specified: %s", p_type) + continue + + if platform.register(hass, p_config, _get_action(hass, p_config)): + _LOGGER.info( + "Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, "")) + else: + _LOGGER.error( + "Error setting up rule %s", p_config.get(CONF_ALIAS, "")) + + return True + + +def _get_action(hass, config): + """ Return an action based on a config. """ + + def action(): + """ Action to be executed. """ + _LOGGER.info("Executing rule %s", config.get(CONF_ALIAS, "")) + + if CONF_SERVICE in config: + domain, service = split_entity_id(config[CONF_SERVICE]) + + service_data = convert( + config.get(CONF_SERVICE_DATA), json.loads, {}) + + if not isinstance(service_data, dict): + _LOGGER.error( + "%s should be a serialized JSON object", CONF_SERVICE_DATA) + service_data = {} + + if CONF_SERVICE_ENTITY_ID in config: + service_data[ATTR_ENTITY_ID] = \ + config[CONF_SERVICE_ENTITY_ID].split(",") + + hass.services.call(domain, service, service_data) + + return action diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py new file mode 100644 index 00000000000..d2be5a54006 --- /dev/null +++ b/homeassistant/components/automation/state.py @@ -0,0 +1,37 @@ +""" +homeassistant.components.automation.state +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Offers state listening automation rules. +""" +import logging + +from homeassistant.const import MATCH_ALL + + +CONF_ENTITY_ID = "state_entity_id" +CONF_FROM = "state_from" +CONF_TO = "state_to" + + +def register(hass, config, action): + """ Listen for state changes based on `config`. """ + entity_id = config.get(CONF_ENTITY_ID) + + if entity_id is None: + logging.getLogger(__name__).error( + "Missing configuration key %s", CONF_ENTITY_ID) + return False + + from_state = config.get(CONF_FROM, MATCH_ALL) + to_state = config.get(CONF_TO, MATCH_ALL) + + # pylint: disable=unused-argument + def state_automation_listener(entity, from_s, to_s): + """ Listens for state changes and calls action. """ + action() + + hass.states.track_change( + entity_id, state_automation_listener, from_state, to_state) + + return True diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py new file mode 100644 index 00000000000..b1fca4121e6 --- /dev/null +++ b/homeassistant/components/automation/time.py @@ -0,0 +1,29 @@ +""" +homeassistant.components.automation.time +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Offers time listening automation rules. +""" +from homeassistant.util import convert + +CONF_HOURS = "time_hours" +CONF_MINUTES = "time_minutes" +CONF_SECONDS = "time_seconds" + + +def register(hass, config, action): + """ Listen for state changes based on `config`. """ + hours = convert(config.get(CONF_HOURS), int) + minutes = convert(config.get(CONF_MINUTES), int) + seconds = convert(config.get(CONF_SECONDS), int) + + # pylint: disable=unused-argument + def time_automation_listener(now): + """ Listens for time changes and calls action. """ + action() + + hass.track_time_change( + time_automation_listener, + hour=hours, minute=minutes, second=seconds) + + return True