Merge pull request #1510 from persandstrom/automation_templating
Use templates in service calls
This commit is contained in:
commit
ddc139b853
3 changed files with 78 additions and 8 deletions
|
@ -10,13 +10,14 @@ from homeassistant.bootstrap import prepare_setup_platform
|
|||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.components import logbook
|
||||
from homeassistant.helpers.service import call_from_config
|
||||
from homeassistant.helpers.service import validate_service_call
|
||||
|
||||
|
||||
DOMAIN = 'automation'
|
||||
|
||||
DEPENDENCIES = ['group']
|
||||
|
||||
CONF_ALIAS = 'alias'
|
||||
CONF_SERVICE = 'service'
|
||||
|
||||
CONF_CONDITION = 'condition'
|
||||
CONF_ACTION = 'action'
|
||||
|
@ -81,8 +82,9 @@ def _setup_automation(hass, config_block, name, config):
|
|||
|
||||
def _get_action(hass, config, name):
|
||||
"""Return an action based on a configuration."""
|
||||
if CONF_SERVICE not in config:
|
||||
_LOGGER.error('Error setting up %s, no action specified.', name)
|
||||
validation_error = validate_service_call(config)
|
||||
if validation_error:
|
||||
_LOGGER.error(validation_error)
|
||||
return None
|
||||
|
||||
def action():
|
||||
|
|
|
@ -3,14 +3,16 @@ import functools
|
|||
import logging
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.helpers.entity import split_entity_id
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
HASS = None
|
||||
|
||||
CONF_SERVICE = 'service'
|
||||
CONF_SERVICE_TEMPLATE = 'service_template'
|
||||
CONF_SERVICE_ENTITY_ID = 'entity_id'
|
||||
CONF_SERVICE_DATA = 'data'
|
||||
CONF_SERVICE_DATA_TEMPLATE = 'data_template'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -28,14 +30,20 @@ def service(domain, service_name):
|
|||
|
||||
def call_from_config(hass, config, blocking=False):
|
||||
"""Call a service based on a config hash."""
|
||||
if not isinstance(config, dict) or CONF_SERVICE not in config:
|
||||
_LOGGER.error('Missing key %s: %s', CONF_SERVICE, config)
|
||||
validation_error = validate_service_call(config)
|
||||
if validation_error:
|
||||
_LOGGER.error(validation_error)
|
||||
return
|
||||
|
||||
domain_service = (
|
||||
config[CONF_SERVICE]
|
||||
if CONF_SERVICE in config
|
||||
else template.render(hass, config[CONF_SERVICE_TEMPLATE]))
|
||||
|
||||
try:
|
||||
domain, service_name = split_entity_id(config[CONF_SERVICE])
|
||||
domain, service_name = domain_service.split('.', 1)
|
||||
except ValueError:
|
||||
_LOGGER.error('Invalid service specified: %s', config[CONF_SERVICE])
|
||||
_LOGGER.error('Invalid service specified: %s', domain_service)
|
||||
return
|
||||
|
||||
service_data = config.get(CONF_SERVICE_DATA)
|
||||
|
@ -48,6 +56,13 @@ def call_from_config(hass, config, blocking=False):
|
|||
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
|
||||
service_data = {}
|
||||
|
||||
service_data_template = config.get(CONF_SERVICE_DATA_TEMPLATE)
|
||||
if service_data_template and isinstance(service_data_template, dict):
|
||||
for key, value in service_data_template.items():
|
||||
service_data[key] = template.render(hass, value)
|
||||
elif service_data_template:
|
||||
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
|
||||
|
||||
entity_id = config.get(CONF_SERVICE_ENTITY_ID)
|
||||
if isinstance(entity_id, str):
|
||||
service_data[ATTR_ENTITY_ID] = [ent.strip() for ent in
|
||||
|
@ -75,3 +90,19 @@ def extract_entity_ids(hass, service_call):
|
|||
return group.expand_entity_ids(hass, [service_ent_id])
|
||||
|
||||
return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)]
|
||||
|
||||
|
||||
def validate_service_call(config):
|
||||
"""Validate service call configuration.
|
||||
|
||||
Helper method to validate that a configuration is a valid service call.
|
||||
Returns None if validation succeeds, else an error description
|
||||
"""
|
||||
if not isinstance(config, dict):
|
||||
return 'Invalid configuration {}'.format(config)
|
||||
if CONF_SERVICE not in config and CONF_SERVICE_TEMPLATE not in config:
|
||||
return 'Missing key {} or {}: {}'.format(
|
||||
CONF_SERVICE,
|
||||
CONF_SERVICE_TEMPLATE,
|
||||
config)
|
||||
return None
|
||||
|
|
|
@ -34,6 +34,25 @@ class TestServiceHelpers(unittest.TestCase):
|
|||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(runs))
|
||||
|
||||
def test_template_service_call(self):
|
||||
""" Test service call with tempating. """
|
||||
config = {
|
||||
'service_template': '{{ \'test_domain.test_service\' }}',
|
||||
'entity_id': 'hello.world',
|
||||
'data_template': {
|
||||
'hello': '{{ \'goodbye\' }}',
|
||||
},
|
||||
}
|
||||
runs = []
|
||||
|
||||
decor = service.service('test_domain', 'test_service')
|
||||
decor(lambda x, y: runs.append(y))
|
||||
|
||||
service.call_from_config(self.hass, config)
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
self.assertEqual('goodbye', runs[0].data['hello'])
|
||||
|
||||
def test_split_entity_string(self):
|
||||
"""Test splitting of entity string."""
|
||||
service.call_from_config(self.hass, {
|
||||
|
@ -97,3 +116,21 @@ class TestServiceHelpers(unittest.TestCase):
|
|||
|
||||
self.assertEqual(['light.ceiling', 'light.kitchen'],
|
||||
service.extract_entity_ids(self.hass, call))
|
||||
|
||||
def test_validate_service_call(self):
|
||||
"""Test is_valid_service_call method"""
|
||||
self.assertNotEqual(
|
||||
service.validate_service_call(
|
||||
{}),
|
||||
None
|
||||
)
|
||||
self.assertEqual(
|
||||
service.validate_service_call(
|
||||
{'service': 'test_domain.test_service'}),
|
||||
None
|
||||
)
|
||||
self.assertEqual(
|
||||
service.validate_service_call(
|
||||
{'service_template': 'test_domain.{{ \'test_service\' }}'}),
|
||||
None
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue