From 0088995b980cf866a1b7712d8344e07efcdcf7dd Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Tue, 26 Nov 2019 11:30:49 +1100 Subject: [PATCH] Refactor template components to extract common routines (#27064) * Added availability_template to Template Cover platform * Added availability_template to Template Binary Sensor platform * Added availability_template to Template Fan platform * Added availability_template to Template Light platform * Added availability_template to Template Sensor platform * Added availability_template to Template Switch platform * Added availability_template to Template Vacuum platform * Added availability_template to Template Lock platform * Added to test for invalid values in availability_template * Black and Lint fix * black formatting * Added to test for invalid values in availability_template * black * Added to test for invalid values in availability_template * Added to test for invalid values in availability_template * simplified exception handler * Fixed Entity discovery big and coverage * Added to test for invalid values in availability_template * flake8 * fixed component ID in test * Added to test for invalid values in availability_template * Added to test for invalid values in availability_template * Made availability_template redering erorr more concise * Cleaned template setup * I'll remember to run black every time one of these days... * Refactored Template initialisation * Refactored Template initialisation * Updated AVAILABILITY_TEMPLATE Rendering error * Updated AVAILABILITY_TEMPLATE Rendering error * Updated AVAILABILITY_TEMPLATE Rendering error * Updated AVAILABILITY_TEMPLATE Rendering error * Updated AVAILABILITY_TEMPLATE Rendering error * Updated AVAILABILITY_TEMPLATE Rendering error * Updated AVAILABILITY_TEMPLATE Rendering error * Updated AVAILABILITY_TEMPLATE Rendering error * Moved const to package Const.py * Moved const to package Const.py * Moved const to package Const.py * Moved const to package Const.py * Moved const to package Const.py * Moved const to package Const.py * Moved const to package Const.py * Moved const to package Const.py * Fix import order (pylint) * Fix import order (pylint) * Fix import order (pylint) * Fix import order (pylint) * Fix import order (pylint) * Fix import order (pylint) * Fix import order (pylint) * Fixed linting issues * Moved availability_template rendering to common loop * Moved availability_template rendering to common loop * Moved availability_template rendering to common loop * Moved availability_template rendering to common loop * Removed 'Magic' string * Removed 'Magic' string and removed duplicate code * Removed 'Magic' string * Removed 'Magic' string * Brought contant into line * Refactored availability_tempalte rendering to common loop * Removed 'Magic' string * Cleaned up const and compare lowercase result to 'true' * Cleaned up const and compare lowercase result to 'true' * Cleaned up const and compare lowercase result to 'true' * Cleaned up const and compare lowercase result to 'true' * Cleaned up const and compare lowercase result to 'true' * Cleaned up const and compare lowercase result to 'true' * Cleaned up const and compare lowercase result to 'true' * reverted _available back to boolean * reverted _available back to boolean * reverted _available back to boolean * reverted _available back to boolean * reverted _available back to boolean * reverted _available back to boolean * reverted _available back to boolean * Fixed tests (magic values and state checks) * Fixed tests (magic values and state checks) * Fixed tests (async, magic values and state checks) * Fixed tests (async, magic values and state checks) * Fixed tests (async, magic values and state checks) * Fixed tests (async, magic values and state checks) * Fixed tests (async, magic values and state checks) * Removed duplicate * Clean up and remove debug * Reverted Dev Container Change --- homeassistant/components/template/__init__.py | 59 +++++++++++++++++++ .../components/template/binary_sensor.py | 52 ++++------------ homeassistant/components/template/cover.py | 48 ++++----------- homeassistant/components/template/fan.py | 37 ++++-------- homeassistant/components/template/light.py | 45 ++++---------- homeassistant/components/template/lock.py | 36 ++++------- homeassistant/components/template/sensor.py | 46 ++++----------- homeassistant/components/template/switch.py | 37 ++---------- homeassistant/components/template/vacuum.py | 48 +++------------ .../components/template/test_binary_sensor.py | 8 +-- tests/components/template/test_lock.py | 9 ++- tests/components/template/test_sensor.py | 53 +++++++++++++++-- 12 files changed, 202 insertions(+), 276 deletions(-) diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index 0c205a0196c..80421b8e3f8 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -1 +1,60 @@ """The template component.""" + +import logging + +from itertools import chain +from homeassistant.const import MATCH_ALL + + +_LOGGER = logging.getLogger(__name__) + + +def initialise_templates(hass, templates, attribute_templates=None): + """Initialise templates and attribute templates.""" + if attribute_templates is None: + attribute_templates = dict() + for template in chain(templates.values(), attribute_templates.values()): + if template is None: + continue + template.hass = hass + + +def extract_entities( + device_name, device_type, manual_entity_ids, templates, attribute_templates=None +): + """Extract entity ids from templates and attribute templates.""" + if attribute_templates is None: + attribute_templates = dict() + entity_ids = set() + if manual_entity_ids is None: + invalid_templates = [] + for template_name, template in chain( + templates.items(), attribute_templates.items() + ): + if template is None: + continue + + template_entity_ids = template.extract_entities() + + if template_entity_ids != MATCH_ALL: + entity_ids |= set(template_entity_ids) + else: + invalid_templates.append(template_name.replace("_template", "")) + + if invalid_templates: + entity_ids = MATCH_ALL + _LOGGER.warning( + "Template %s '%s' has no entity ids configured to track nor" + " were we able to extract the entities to track from the %s " + "template(s). This entity will only be able to be updated " + "manually.", + device_type, + device_name, + ", ".join(invalid_templates), + ) + else: + entity_ids = list(entity_ids) + else: + entity_ids = manual_entity_ids + + return entity_ids diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index d5ade703c97..116862abc79 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -1,6 +1,5 @@ """Support for exposing a templated binary sensor.""" import logging -from itertools import chain import voluptuous as vol @@ -26,6 +25,7 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_state_change, async_track_same_state +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) @@ -63,11 +63,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= icon_template = device_config.get(CONF_ICON_TEMPLATE) entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE) availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) - entity_ids = set() - manual_entity_ids = device_config.get(ATTR_ENTITY_ID) attribute_templates = device_config.get(CONF_ATTRIBUTE_TEMPLATES, {}) - invalid_templates = [] + friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) + device_class = device_config.get(CONF_DEVICE_CLASS) + delay_on = device_config.get(CONF_DELAY_ON) + delay_off = device_config.get(CONF_DELAY_OFF) templates = { CONF_VALUE_TEMPLATE: value_template, @@ -76,41 +77,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= CONF_AVAILABILITY_TEMPLATE: availability_template, } - for tpl_name, template in chain(templates.items(), attribute_templates.items()): - if template is None: - continue - template.hass = hass - - if manual_entity_ids is not None: - continue - - template_entity_ids = template.extract_entities() - if template_entity_ids == MATCH_ALL: - entity_ids = MATCH_ALL - # Cut off _template from name - invalid_templates.append(tpl_name.replace("_template", "")) - elif entity_ids != MATCH_ALL: - entity_ids |= set(template_entity_ids) - - if manual_entity_ids is not None: - entity_ids = manual_entity_ids - elif entity_ids != MATCH_ALL: - entity_ids = list(entity_ids) - - if invalid_templates: - _LOGGER.warning( - "Template binary sensor %s has no entity ids configured to" - " track nor were we able to extract the entities to track" - " from the %s template(s). This entity will only be able" - " to be updated manually.", - device, - ", ".join(invalid_templates), - ) - - friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) - device_class = device_config.get(CONF_DEVICE_CLASS) - delay_on = device_config.get(CONF_DELAY_ON) - delay_off = device_config.get(CONF_DELAY_OFF) + initialise_templates(hass, templates, attribute_templates) + entity_ids = extract_entities( + device, + "binary sensor", + device_config.get(ATTR_ENTITY_ID), + templates, + attribute_templates, + ) sensors.append( BinarySensorTemplate( diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 483ee1ae872..22035af24ec 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -24,7 +24,6 @@ from homeassistant.const import ( CONF_FRIENDLY_NAME, CONF_ENTITY_ID, EVENT_HOMEASSISTANT_START, - MATCH_ALL, CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_DEVICE_CLASS, @@ -38,6 +37,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) @@ -100,13 +100,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= covers = [] for device, device_config in config[CONF_COVERS].items(): - friendly_name = device_config.get(CONF_FRIENDLY_NAME, device) state_template = device_config.get(CONF_VALUE_TEMPLATE) position_template = device_config.get(CONF_POSITION_TEMPLATE) tilt_template = device_config.get(CONF_TILT_TEMPLATE) icon_template = device_config.get(CONF_ICON_TEMPLATE) availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE) + + friendly_name = device_config.get(CONF_FRIENDLY_NAME, device) device_class = device_config.get(CONF_DEVICE_CLASS) open_action = device_config.get(OPEN_ACTION) close_action = device_config.get(CLOSE_ACTION) @@ -121,41 +122,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "Must specify at least one of %s" or "%s", OPEN_ACTION, POSITION_ACTION ) continue - template_entity_ids = set() - if state_template is not None: - temp_ids = state_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - if position_template is not None: - temp_ids = position_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) + templates = { + CONF_VALUE_TEMPLATE: state_template, + CONF_POSITION_TEMPLATE: position_template, + CONF_TILT_TEMPLATE: tilt_template, + CONF_ICON_TEMPLATE: icon_template, + CONF_AVAILABILITY_TEMPLATE: availability_template, + CONF_ENTITY_PICTURE_TEMPLATE: entity_picture_template, + } - if tilt_template is not None: - temp_ids = tilt_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if icon_template is not None: - temp_ids = icon_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if entity_picture_template is not None: - temp_ids = entity_picture_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if availability_template is not None: - temp_ids = availability_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if not template_entity_ids: - template_entity_ids = MATCH_ALL - - entity_ids = device_config.get(CONF_ENTITY_ID, template_entity_ids) + initialise_templates(hass, templates) + entity_ids = extract_entities(device, "cover", None, templates) covers.append( CoverTemplate( diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 606f18e5fe1..ebb9bcc8b14 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -25,7 +25,6 @@ from homeassistant.const import ( CONF_ENTITY_ID, STATE_ON, STATE_OFF, - MATCH_ALL, EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, ) @@ -33,6 +32,7 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) @@ -98,33 +98,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= speed_list = device_config[CONF_SPEED_LIST] - entity_ids = set() - manual_entity_ids = device_config.get(CONF_ENTITY_ID) + templates = { + CONF_VALUE_TEMPLATE: state_template, + CONF_SPEED_TEMPLATE: speed_template, + CONF_OSCILLATING_TEMPLATE: oscillating_template, + CONF_DIRECTION_TEMPLATE: direction_template, + CONF_AVAILABILITY_TEMPLATE: availability_template, + } - for template in ( - state_template, - speed_template, - oscillating_template, - direction_template, - availability_template, - ): - if template is None: - continue - template.hass = hass - - if entity_ids == MATCH_ALL or manual_entity_ids is not None: - continue - - template_entity_ids = template.extract_entities() - if template_entity_ids == MATCH_ALL: - entity_ids = MATCH_ALL - else: - entity_ids |= set(template_entity_ids) - - if manual_entity_ids is not None: - entity_ids = manual_entity_ids - elif entity_ids != MATCH_ALL: - entity_ids = list(entity_ids) + initialise_templates(hass, templates) + entity_ids = extract_entities(device, "fan", None, templates) fans.append( TemplateFan( diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 552c21f170d..b71aadd0155 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -19,7 +19,6 @@ from homeassistant.const import ( STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START, - MATCH_ALL, CONF_LIGHTS, ) from homeassistant.helpers.config_validation import PLATFORM_SCHEMA @@ -28,6 +27,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) @@ -64,46 +64,27 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= for device, device_config in config[CONF_LIGHTS].items(): friendly_name = device_config.get(CONF_FRIENDLY_NAME, device) + state_template = device_config.get(CONF_VALUE_TEMPLATE) icon_template = device_config.get(CONF_ICON_TEMPLATE) entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE) availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) + level_template = device_config.get(CONF_LEVEL_TEMPLATE) + on_action = device_config[CONF_ON_ACTION] off_action = device_config[CONF_OFF_ACTION] level_action = device_config.get(CONF_LEVEL_ACTION) - level_template = device_config.get(CONF_LEVEL_TEMPLATE) - template_entity_ids = set() + templates = { + CONF_VALUE_TEMPLATE: state_template, + CONF_ICON_TEMPLATE: icon_template, + CONF_ENTITY_PICTURE_TEMPLATE: entity_picture_template, + CONF_AVAILABILITY_TEMPLATE: availability_template, + CONF_LEVEL_TEMPLATE: level_template, + } - if state_template is not None: - temp_ids = state_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if level_template is not None: - temp_ids = level_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if icon_template is not None: - temp_ids = icon_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if entity_picture_template is not None: - temp_ids = entity_picture_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if availability_template is not None: - temp_ids = availability_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) - - if not template_entity_ids: - template_entity_ids = MATCH_ALL - - entity_ids = device_config.get(CONF_ENTITY_ID, template_entity_ids) + initialise_templates(hass, templates) + entity_ids = extract_entities(device, "light", None, templates) lights.append( LightTemplate( diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index aa8cc8b1224..71e9cc6642d 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -19,6 +19,7 @@ from homeassistant.const import ( from homeassistant.exceptions import TemplateError from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) @@ -43,39 +44,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the Template lock.""" - name = config.get(CONF_NAME) + device = config.get(CONF_NAME) value_template = config.get(CONF_VALUE_TEMPLATE) - value_template.hass = hass - value_template_entity_ids = value_template.extract_entities() - - if value_template_entity_ids == MATCH_ALL: - _LOGGER.warning( - "Template lock '%s' has no entity ids configured to track nor " - "were we able to extract the entities to track from the '%s' " - "template. This entity will only be able to be updated " - "manually.", - name, - CONF_VALUE_TEMPLATE, - ) - - template_entity_ids = set() - template_entity_ids |= set(value_template_entity_ids) - availability_template = config.get(CONF_AVAILABILITY_TEMPLATE) - if availability_template is not None: - availability_template.hass = hass - temp_ids = availability_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - template_entity_ids |= set(temp_ids) + + templates = { + CONF_VALUE_TEMPLATE: value_template, + CONF_AVAILABILITY_TEMPLATE: availability_template, + } + + initialise_templates(hass, templates) + entity_ids = extract_entities(device, "lock", None, templates) async_add_devices( [ TemplateLock( hass, - name, + device, value_template, availability_template, - template_entity_ids, + entity_ids, config.get(CONF_LOCK), config.get(CONF_UNLOCK), config.get(CONF_OPTIMISTIC), diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index a8768193736..4ea7daa54f6 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -1,7 +1,6 @@ """Allows the creation of a sensor that breaks out state_attributes.""" import logging from typing import Optional -from itertools import chain import voluptuous as vol @@ -29,6 +28,7 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.event import async_track_state_change +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE CONF_ATTRIBUTE_TEMPLATES = "attribute_templates" @@ -72,10 +72,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= device_class = device_config.get(CONF_DEVICE_CLASS) attribute_templates = device_config[CONF_ATTRIBUTE_TEMPLATES] - entity_ids = set() - manual_entity_ids = device_config.get(ATTR_ENTITY_ID) - invalid_templates = [] - templates = { CONF_VALUE_TEMPLATE: state_template, CONF_ICON_TEMPLATE: icon_template, @@ -84,36 +80,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= CONF_AVAILABILITY_TEMPLATE: availability_template, } - for tpl_name, template in chain(templates.items(), attribute_templates.items()): - if template is None: - continue - template.hass = hass - - if manual_entity_ids is not None: - continue - - template_entity_ids = template.extract_entities() - if template_entity_ids == MATCH_ALL: - entity_ids = MATCH_ALL - # Cut off _template from name - invalid_templates.append(tpl_name.replace("_template", "")) - elif entity_ids != MATCH_ALL: - entity_ids |= set(template_entity_ids) - - if invalid_templates: - _LOGGER.warning( - "Template sensor %s has no entity ids configured to track nor" - " were we able to extract the entities to track from the %s " - "template(s). This entity will only be able to be updated " - "manually.", - device, - ", ".join(invalid_templates), - ) - - if manual_entity_ids is not None: - entity_ids = manual_entity_ids - elif entity_ids != MATCH_ALL: - entity_ids = list(entity_ids) + initialise_templates(hass, templates, attribute_templates) + entity_ids = extract_entities( + device, + "sensor", + device_config.get(ATTR_ENTITY_ID), + templates, + attribute_templates, + ) sensors.append( SensorTemplate( @@ -131,7 +105,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= attribute_templates, ) ) + async_add_entities(sensors) + return True diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 2d4dda032ca..e06ca0c8d54 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -19,13 +19,13 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_SWITCHES, EVENT_HOMEASSISTANT_START, - MATCH_ALL, ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) @@ -64,8 +64,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) on_action = device_config[ON_ACTION] off_action = device_config[OFF_ACTION] - manual_entity_ids = device_config.get(ATTR_ENTITY_ID) - entity_ids = set() templates = { CONF_VALUE_TEMPLATE: state_template, @@ -73,35 +71,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= CONF_ENTITY_PICTURE_TEMPLATE: entity_picture_template, CONF_AVAILABILITY_TEMPLATE: availability_template, } - invalid_templates = [] - for template_name, template in templates.items(): - if template is not None: - template.hass = hass - - if manual_entity_ids is not None: - continue - - template_entity_ids = template.extract_entities() - if template_entity_ids == MATCH_ALL: - invalid_templates.append(template_name.replace("_template", "")) - entity_ids = MATCH_ALL - elif entity_ids != MATCH_ALL: - entity_ids |= set(template_entity_ids) - if invalid_templates: - _LOGGER.warning( - "Template sensor %s has no entity ids configured to track nor" - " were we able to extract the entities to track from the %s " - "template(s). This entity will only be able to be updated " - "manually.", - device, - ", ".join(invalid_templates), - ) - else: - if manual_entity_ids is None: - entity_ids = list(entity_ids) - else: - entity_ids = manual_entity_ids + initialise_templates(hass, templates) + entity_ids = extract_entities( + device, "switch", device_config.get(ATTR_ENTITY_ID), templates + ) switches.append( SwitchTemplate( @@ -117,6 +91,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= entity_ids, ) ) + if not switches: _LOGGER.error("No switches added") return False diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 6a6523514c4..8201842e131 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -43,7 +43,7 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script - +from . import extract_entities, initialise_templates from .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) @@ -109,45 +109,15 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= fan_speed_list = device_config[CONF_FAN_SPEED_LIST] - entity_ids = set() - manual_entity_ids = device_config.get(CONF_ENTITY_ID) - invalid_templates = [] + templates = { + CONF_VALUE_TEMPLATE: state_template, + CONF_BATTERY_LEVEL_TEMPLATE: battery_level_template, + CONF_FAN_SPEED_TEMPLATE: fan_speed_template, + CONF_AVAILABILITY_TEMPLATE: availability_template, + } - for tpl_name, template in ( - (CONF_VALUE_TEMPLATE, state_template), - (CONF_BATTERY_LEVEL_TEMPLATE, battery_level_template), - (CONF_FAN_SPEED_TEMPLATE, fan_speed_template), - (CONF_AVAILABILITY_TEMPLATE, availability_template), - ): - if template is None: - continue - template.hass = hass - - if manual_entity_ids is not None: - continue - - template_entity_ids = template.extract_entities() - if template_entity_ids == MATCH_ALL: - entity_ids = MATCH_ALL - # Cut off _template from name - invalid_templates.append(tpl_name[:-9]) - elif entity_ids != MATCH_ALL: - entity_ids |= set(template_entity_ids) - - if invalid_templates: - _LOGGER.warning( - "Template vacuum %s has no entity ids configured to track nor" - " were we able to extract the entities to track from the %s " - "template(s). This entity will only be able to be updated " - "manually.", - device, - ", ".join(invalid_templates), - ) - - if manual_entity_ids is not None: - entity_ids = manual_entity_ids - elif entity_ids != MATCH_ALL: - entity_ids = list(entity_ids) + initialise_templates(hass, templates) + entity_ids = extract_entities(device, "vacuum", None, templates) vacuums.append( TemplateVacuum( diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 143811da209..b17b98f2f10 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -576,22 +576,22 @@ async def test_no_update_template_match_all(hass, caplog): await hass.async_block_till_done() assert len(hass.states.async_all()) == 5 assert ( - "Template binary sensor all_state has no entity ids " + "Template binary sensor 'all_state' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the value template" ) in caplog.text assert ( - "Template binary sensor all_icon has no entity ids " + "Template binary sensor 'all_icon' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the icon template" ) in caplog.text assert ( - "Template binary sensor all_entity_picture has no entity ids " + "Template binary sensor 'all_entity_picture' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the entity_picture template" ) in caplog.text assert ( - "Template binary sensor all_attribute has no entity ids " + "Template binary sensor 'all_attribute' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the test_attribute template" ) in caplog.text diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index d1d30207375..32a34411b33 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -254,10 +254,9 @@ class TestTemplateLock: assert state.state == lock.STATE_UNLOCKED assert ( - "Template lock 'Template Lock' has no entity ids configured " - "to track nor were we able to extract the entities to track " - "from the 'value_template' template. This entity will only " - "be able to be updated manually." + "Template lock 'Template Lock' has no entity ids configured to track " + "nor were we able to extract the entities to track from the value " + "template(s). This entity will only be able to be updated manually" ) in caplog.text self.hass.states.set("lock.template_lock", lock.STATE_LOCKED) @@ -343,7 +342,7 @@ async def test_available_template_with_entities(hass): { "lock": { "platform": "template", - "value_template": "{{ 'on' }}", + "value_template": "{{ states('switch.test_state') }}", "lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"}, "unlock": { "service": "switch.turn_off", diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index b3813da1766..7ae34b04c00 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -377,6 +377,49 @@ class TestTemplateSensor: state = self.hass.states.get("sensor.test2") assert "device_class" not in state.attributes + def test_available_template_with_entities(self): + """Test availability tempalates with values from other entities.""" + + with assert_setup_component(1): + assert setup_component( + self.hass, + "sensor", + { + "sensor": { + "platform": "template", + "sensors": { + "test_template_sensor": { + "value_template": "{{ states.sensor.test_state.state }}", + "availability_template": "{{ is_state('availability_boolean.state', 'on') }}", + } + }, + } + }, + ) + + self.hass.start() + self.hass.block_till_done() + + # When template returns true.. + self.hass.states.set("availability_boolean.state", STATE_ON) + self.hass.block_till_done() + + # Device State should not be unavailable + assert ( + self.hass.states.get("sensor.test_template_sensor").state + != STATE_UNAVAILABLE + ) + + # When Availability template returns false + self.hass.states.set("availability_boolean.state", STATE_OFF) + self.hass.block_till_done() + + # device state should be unavailable + assert ( + self.hass.states.get("sensor.test_template_sensor").state + == STATE_UNAVAILABLE + ) + async def test_available_template_with_entities(hass): """Test availability tempalates with values from other entities.""" @@ -511,27 +554,27 @@ async def test_no_template_match_all(hass, caplog): await hass.async_block_till_done() assert len(hass.states.async_all()) == 6 assert ( - "Template sensor invalid_state has no entity ids " + "Template sensor 'invalid_state' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the value template" ) in caplog.text assert ( - "Template sensor invalid_icon has no entity ids " + "Template sensor 'invalid_icon' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the icon template" ) in caplog.text assert ( - "Template sensor invalid_entity_picture has no entity ids " + "Template sensor 'invalid_entity_picture' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the entity_picture template" ) in caplog.text assert ( - "Template sensor invalid_friendly_name has no entity ids " + "Template sensor 'invalid_friendly_name' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the friendly_name template" ) in caplog.text assert ( - "Template sensor invalid_attribute has no entity ids " + "Template sensor 'invalid_attribute' has no entity ids " "configured to track nor were we able to extract the entities to " "track from the test_attribute template" ) in caplog.text