2015-01-16 07:32:27 +00:00
|
|
|
"""
|
|
|
|
homeassistant.components.automation
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Allows to setup simple automation rules via the config file.
|
|
|
|
"""
|
|
|
|
import logging
|
|
|
|
|
2015-08-10 00:12:22 +00:00
|
|
|
from homeassistant.bootstrap import prepare_setup_platform
|
2015-03-15 02:13:03 +00:00
|
|
|
from homeassistant.util import split_entity_id
|
2015-09-14 05:25:42 +00:00
|
|
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
|
2015-09-14 07:02:33 +00:00
|
|
|
from homeassistant.components import logbook
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
DOMAIN = 'automation'
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
DEPENDENCIES = ['group']
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
CONF_ALIAS = 'alias'
|
|
|
|
CONF_SERVICE = 'execute_service'
|
|
|
|
CONF_SERVICE_ENTITY_ID = 'service_entity_id'
|
|
|
|
CONF_SERVICE_DATA = 'service_data'
|
2015-09-15 05:05:40 +00:00
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
CONF_CONDITION = 'condition'
|
2015-09-15 05:05:40 +00:00
|
|
|
CONF_ACTION = 'action'
|
2015-09-15 15:56:06 +00:00
|
|
|
CONF_TRIGGER = 'trigger'
|
|
|
|
CONF_CONDITION_TYPE = 'condition_type'
|
|
|
|
|
|
|
|
CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
|
|
|
|
CONDITION_TYPE_AND = 'and'
|
|
|
|
CONDITION_TYPE_OR = 'or'
|
2015-09-15 05:51:28 +00:00
|
|
|
|
|
|
|
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
|
2015-01-16 07:32:27 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def setup(hass, config):
|
|
|
|
""" Sets up automation. """
|
2015-09-15 05:05:40 +00:00
|
|
|
config_key = DOMAIN
|
|
|
|
found = 1
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-15 05:05:40 +00:00
|
|
|
while config_key in config:
|
2015-09-18 16:12:27 +00:00
|
|
|
# check for one block syntax
|
|
|
|
if isinstance(config[config_key], dict):
|
|
|
|
config_block = _migrate_old_config(config[config_key])
|
|
|
|
name = config_block.get(CONF_ALIAS, config_key)
|
|
|
|
_setup_automation(hass, config_block, name, config)
|
|
|
|
|
|
|
|
# check for multiple block syntax
|
|
|
|
elif isinstance(config[config_key], list):
|
|
|
|
list_no = 0
|
|
|
|
for config_block in config[config_key]:
|
2015-09-19 13:51:50 +00:00
|
|
|
name = config_block.get(CONF_ALIAS,
|
|
|
|
"{}, {}".format(config_key, list_no))
|
2015-09-18 16:12:27 +00:00
|
|
|
list_no += 1
|
|
|
|
config_block = _migrate_old_config(config_block)
|
|
|
|
_setup_automation(hass, config_block, name, config)
|
|
|
|
|
|
|
|
# any scalar value is incorrect
|
|
|
|
else:
|
|
|
|
_LOGGER.error('Error in config in section %s.', config_key)
|
|
|
|
|
2015-09-15 05:05:40 +00:00
|
|
|
found += 1
|
|
|
|
config_key = "{} {}".format(DOMAIN, found)
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-18 16:12:27 +00:00
|
|
|
return True
|
2015-09-14 05:25:42 +00:00
|
|
|
|
2015-09-19 13:51:50 +00:00
|
|
|
|
2015-09-18 16:12:27 +00:00
|
|
|
def _setup_automation(hass, config_block, name, config):
|
|
|
|
""" Setup one instance of automation """
|
2015-09-14 07:02:33 +00:00
|
|
|
|
2015-09-18 16:12:27 +00:00
|
|
|
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
|
2015-09-15 05:51:28 +00:00
|
|
|
|
2015-09-18 16:12:27 +00:00
|
|
|
if action is None:
|
|
|
|
return False
|
2015-09-14 05:25:42 +00:00
|
|
|
|
2015-09-19 13:51:50 +00:00
|
|
|
if CONF_CONDITION in config_block or CONF_CONDITION_TYPE in config_block:
|
2015-09-18 16:12:27 +00:00
|
|
|
action = _process_if(hass, config, config_block, action)
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-18 16:12:27 +00:00
|
|
|
if action is None:
|
|
|
|
return False
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-18 16:12:27 +00:00
|
|
|
_process_trigger(hass, config, config_block.get(CONF_TRIGGER, []), name,
|
|
|
|
action)
|
|
|
|
return True
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-19 13:51:50 +00:00
|
|
|
|
2015-09-15 05:05:40 +00:00
|
|
|
def _get_action(hass, config, name):
|
2015-01-16 07:32:27 +00:00
|
|
|
""" Return an action based on a config. """
|
|
|
|
|
2015-09-14 07:02:33 +00:00
|
|
|
if CONF_SERVICE not in config:
|
2015-09-15 05:05:40 +00:00
|
|
|
_LOGGER.error('Error setting up %s, no action specified.', name)
|
|
|
|
return None
|
2015-09-14 07:02:33 +00:00
|
|
|
|
2015-01-16 07:32:27 +00:00
|
|
|
def action():
|
|
|
|
""" Action to be executed. """
|
2015-09-14 07:02:33 +00:00
|
|
|
_LOGGER.info('Executing %s', name)
|
|
|
|
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-14 07:02:33 +00:00
|
|
|
domain, service = split_entity_id(config[CONF_SERVICE])
|
|
|
|
service_data = config.get(CONF_SERVICE_DATA, {})
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-14 07:02:33 +00:00
|
|
|
if not isinstance(service_data, dict):
|
|
|
|
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
|
|
|
|
service_data = {}
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-14 07:02:33 +00:00
|
|
|
if CONF_SERVICE_ENTITY_ID in config:
|
|
|
|
try:
|
|
|
|
service_data[ATTR_ENTITY_ID] = \
|
|
|
|
config[CONF_SERVICE_ENTITY_ID].split(",")
|
|
|
|
except AttributeError:
|
|
|
|
service_data[ATTR_ENTITY_ID] = \
|
|
|
|
config[CONF_SERVICE_ENTITY_ID]
|
2015-01-16 07:32:27 +00:00
|
|
|
|
2015-09-14 07:02:33 +00:00
|
|
|
hass.services.call(domain, service, service_data)
|
2015-01-16 07:32:27 +00:00
|
|
|
|
|
|
|
return action
|
2015-09-14 05:25:42 +00:00
|
|
|
|
|
|
|
|
2015-09-15 05:05:40 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
def _process_if(hass, config, p_config, action):
|
2015-09-14 05:25:42 +00:00
|
|
|
""" Processes if checks. """
|
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
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]
|
|
|
|
|
2015-09-14 05:25:42 +00:00
|
|
|
if isinstance(if_configs, dict):
|
|
|
|
if_configs = [if_configs]
|
|
|
|
|
2015-09-15 05:51:28 +00:00
|
|
|
checks = []
|
2015-09-14 05:25:42 +00:00
|
|
|
for if_config in if_configs:
|
2015-09-15 15:56:06 +00:00
|
|
|
platform = _resolve_platform('if_action', hass, config,
|
2015-09-15 05:05:40 +00:00
|
|
|
if_config.get(CONF_PLATFORM))
|
|
|
|
if platform is None:
|
2015-09-14 05:25:42 +00:00
|
|
|
continue
|
|
|
|
|
2015-09-15 05:51:28 +00:00
|
|
|
check = platform.if_action(hass, if_config)
|
2015-09-15 05:05:40 +00:00
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
# Invalid conditions are allowed if we base it on trigger
|
|
|
|
if check is None and not use_trigger:
|
2015-09-15 05:51:28 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
checks.append(check)
|
|
|
|
|
|
|
|
if cond_type == CONDITION_TYPE_AND:
|
|
|
|
def if_action():
|
2015-09-15 07:11:24 +00:00
|
|
|
""" AND all conditions. """
|
2015-09-15 05:51:28 +00:00
|
|
|
if all(check() for check in checks):
|
|
|
|
action()
|
|
|
|
else:
|
|
|
|
def if_action():
|
2015-09-15 07:11:24 +00:00
|
|
|
""" OR all conditions. """
|
2015-09-15 05:51:28 +00:00
|
|
|
if any(check() for check in checks):
|
|
|
|
action()
|
|
|
|
|
|
|
|
return if_action
|
2015-09-14 05:25:42 +00:00
|
|
|
|
2015-09-15 05:05:40 +00:00
|
|
|
|
|
|
|
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:
|
2015-09-14 05:25:42 +00:00
|
|
|
continue
|
|
|
|
|
2015-09-15 05:05:40 +00:00
|
|
|
if platform.trigger(hass, conf, action):
|
|
|
|
_LOGGER.info("Initialized rule %s", name)
|
|
|
|
else:
|
|
|
|
_LOGGER.error("Error setting up rule %s", name)
|
2015-09-14 05:25:42 +00:00
|
|
|
|
2015-09-15 05:05:40 +00:00
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
def _resolve_platform(method, hass, config, platform):
|
2015-09-15 05:05:40 +00:00
|
|
|
""" Find automation platform. """
|
|
|
|
if platform is None:
|
|
|
|
return None
|
|
|
|
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
|
|
|
|
|
2015-09-15 15:56:06 +00:00
|
|
|
if platform is None or not hasattr(platform, method):
|
2015-09-15 05:05:40 +00:00
|
|
|
_LOGGER.error("Unknown automation platform specified for %s: %s",
|
2015-09-15 15:56:06 +00:00
|
|
|
method, platform)
|
2015-09-15 05:05:40 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
return platform
|