From 41a0a4f7c4f9abe3bc4c3f631ba998eba21d57ae Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 8 Sep 2019 08:18:06 +0000 Subject: [PATCH 01/13] Added availability_template to Template Switch platform --- homeassistant/components/template/switch.py | 31 +++++++++++-- homeassistant/const.py | 1 + tests/components/template/test_switch.py | 51 ++++++++++++++++++++- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index c77a90c1f8b6ee..578b0ae966b4ba 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -14,11 +14,13 @@ CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, + CONF_AVAILABILITY_TEMPLATE, STATE_OFF, STATE_ON, ATTR_ENTITY_ID, CONF_SWITCHES, EVENT_HOMEASSISTANT_START, + MATCH_ALL, ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv @@ -37,6 +39,7 @@ vol.Required(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_ICON_TEMPLATE): cv.template, vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template, + vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Required(ON_ACTION): cv.SCRIPT_SCHEMA, vol.Required(OFF_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(ATTR_FRIENDLY_NAME): cv.string, @@ -58,6 +61,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= state_template = device_config[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) on_action = device_config[ON_ACTION] off_action = device_config[OFF_ACTION] entity_ids = ( @@ -72,6 +76,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if entity_picture_template is not None: entity_picture_template.hass = hass + if availability_template is not None: + availability_template.hass = hass + temp_ids = state_template.extract_entities() + if str(temp_ids) != MATCH_ALL: + entity_ids |= set(temp_ids) + switches.append( SwitchTemplate( hass, @@ -80,6 +90,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= state_template, icon_template, entity_picture_template, + availability_template, on_action, off_action, entity_ids, @@ -104,6 +115,7 @@ def __init__( state_template, icon_template, entity_picture_template, + availability_template, on_action, off_action, entity_ids, @@ -120,9 +132,11 @@ def __init__( self._state = False self._icon_template = icon_template self._entity_picture_template = entity_picture_template + self._availability_template = availability_template self._icon = None self._entity_picture = None self._entities = entity_ids + self._available = True async def async_added_to_hass(self): """Register callbacks.""" @@ -160,11 +174,6 @@ def should_poll(self): """Return the polling state.""" return False - @property - def available(self): - """If switch is available.""" - return self._state is not None - @property def icon(self): """Return the icon to use in the frontend, if any.""" @@ -175,6 +184,11 @@ def entity_picture(self): """Return the entity_picture to use in the frontend, if any.""" return self._entity_picture + @property + def available(self) -> bool: + """Return if the device is available.""" + return self._available + async def async_turn_on(self, **kwargs): """Fire the on action.""" await self._on_script.async_run(context=self._context) @@ -233,3 +247,10 @@ async def async_update(self): self._name, ex, ) + if self._availability_template is not None: + try: + result = self._availability_template.async_render() + self._available = result == "true" + except (TemplateError, ValueError) as err: + _LOGGER.error(err) + self._available = True diff --git a/homeassistant/const.py b/homeassistant/const.py index 4cfd16b8c9f0cc..5eb351916190ba 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -35,6 +35,7 @@ CONF_AUTHENTICATION = "authentication" CONF_AUTH_MFA_MODULES = "auth_mfa_modules" CONF_AUTH_PROVIDERS = "auth_providers" +CONF_AVAILABILITY_TEMPLATE = "availability_template" CONF_BASE = "base" CONF_BEFORE = "before" CONF_BELOW = "below" diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index 9a07a935d12546..5842329ed1a557 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -1,7 +1,7 @@ """The tests for the Template switch platform.""" from homeassistant.core import callback from homeassistant import setup -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE from tests.common import get_test_home_assistant, assert_setup_component from tests.components.switch import common @@ -135,6 +135,55 @@ def test_template_state_boolean_off(self): state = self.hass.states.get("switch.test_template_switch") assert state.state == STATE_OFF + def test_available_template_with_entities(self): + """Test availability templates with values from other entities.""" + availability_template = """ + {% if is_state('availability_boolean.state', 'True') %} + {{ 'true' }} + {% else %} + {{ 'false' }} + {% endif %} + """ + with assert_setup_component(1, "switch"): + assert setup.setup_component( + self.hass, + "switch", + { + "switch": { + "platform": "template", + "switches": { + "test_template_switch": { + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "switch.turn_on", + "entity_id": "switch.test_state", + }, + "turn_off": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + "availability_template": availability_template, + } + }, + } + }, + ) + + self.hass.start() + self.hass.block_till_done() + + self.hass.states.set("availability_boolean.state", True) + self.hass.block_till_done() + + state = self.hass.states.get("switch.test_template_switch") + assert state.state != STATE_UNAVAILABLE + + self.hass.states.set("availability_boolean.state", False) + self.hass.block_till_done() + + state = self.hass.states.get("switch.test_template_switch") + assert state.state == STATE_UNAVAILABLE + def test_icon_template(self): """Test icon template.""" with assert_setup_component(1, "switch"): From 85e72b4f0497ba5b242697339294cf499d6eae7d Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 8 Sep 2019 22:41:31 +0000 Subject: [PATCH 02/13] Fixed Entity discovery big and coverage --- homeassistant/components/template/switch.py | 9 ++++-- tests/components/template/test_switch.py | 35 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 578b0ae966b4ba..339f4d0a762038 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -78,9 +78,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if availability_template is not None: availability_template.hass = hass - temp_ids = state_template.extract_entities() - if str(temp_ids) != MATCH_ALL: - entity_ids |= set(temp_ids) + temp_ids = availability_template.extract_entities() + if str(entity_ids) != MATCH_ALL: + if (temp_ids) == MATCH_ALL: + entity_ids |= MATCH_ALL + else: + entity_ids |= set(temp_ids) switches.append( SwitchTemplate( diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index 5842329ed1a557..07ac320b38f764 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -184,6 +184,41 @@ def test_available_template_with_entities(self): state = self.hass.states.get("switch.test_template_switch") assert state.state == STATE_UNAVAILABLE + def test_invalid_availability_template_keeps_component_available(self, caplog): + """Test that an invalid availability keeps the device available.""" + with assert_setup_component(1, "switch"): + assert setup.setup_component( + self.hass, + "switch", + { + "switch": { + "platform": "template", + "switches": { + "test_template_switch": { + "value_template": "{{ true }}", + "turn_on": { + "service": "switch.turn_on", + "entity_id": "switch.test_state", + }, + "turn_off": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + "availability_template": "{{ x - 12 }}", + } + }, + } + }, + ) + + + self.hass.start() + self.hass.block_till_done() + + state = self.hass.states.get("switch.test_template_switch") + assert state.state != STATE_UNAVAILABLE + assert ("UndefinedError: 'x' is undefined") in caplog.text + def test_icon_template(self): """Test icon template.""" with assert_setup_component(1, "switch"): From ecb3a6d2e31b6db86643224078cb44b49c5dfa10 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 8 Sep 2019 22:56:56 +0000 Subject: [PATCH 03/13] flake8 --- tests/components/template/test_switch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index 07ac320b38f764..5e5ff967b67298 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -211,7 +211,6 @@ def test_invalid_availability_template_keeps_component_available(self, caplog): }, ) - self.hass.start() self.hass.block_till_done() From 0b369c8e43cc2dbf78dab22597fb60ee3e742339 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 9 Sep 2019 00:56:53 +0000 Subject: [PATCH 04/13] Cleaned template setup --- homeassistant/components/template/switch.py | 23 +++++++-------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 339f4d0a762038..afa6d4b154764b 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -64,26 +64,19 @@ 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] - entity_ids = ( + entity_ids = set( device_config.get(ATTR_ENTITY_ID) or state_template.extract_entities() ) - state_template.hass = hass - if icon_template is not None: - icon_template.hass = hass - - if entity_picture_template is not None: - entity_picture_template.hass = hass + templates = [icon_template, entity_picture_template, availability_template] - if availability_template is not None: - availability_template.hass = hass - temp_ids = availability_template.extract_entities() - if str(entity_ids) != MATCH_ALL: - if (temp_ids) == MATCH_ALL: - entity_ids |= MATCH_ALL - else: - entity_ids |= set(temp_ids) + for template in templates: + if template is not None: + template.hass = hass + temp_entities = template.extract_entities() + if temp_entities is not None: + entity_ids |= set(temp_entities) switches.append( SwitchTemplate( From 341c3bcc1b5cd3ca97f7211ece3bb59c947e5693 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 9 Sep 2019 02:02:47 +0000 Subject: [PATCH 05/13] I'll remember to run black every time one of these days... --- homeassistant/components/template/switch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index afa6d4b154764b..46fdf12caa2d0e 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -20,7 +20,6 @@ ATTR_ENTITY_ID, CONF_SWITCHES, EVENT_HOMEASSISTANT_START, - MATCH_ALL, ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv From ad3648b4ad1c9442cd5f2def1a2aa12fecd93832 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Fri, 13 Sep 2019 00:12:30 +0000 Subject: [PATCH 06/13] Updated AVAILABILITY_TEMPLATE Rendering error --- homeassistant/components/template/switch.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 46fdf12caa2d0e..bec63fd4539745 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -246,6 +246,11 @@ async def async_update(self): try: result = self._availability_template.async_render() self._available = result == "true" - except (TemplateError, ValueError) as err: - _LOGGER.error(err) + except (TemplateError, ValueError) as ex: + _LOGGER.error( + "Could not render %s template %s: %s", + friendly_property_name, + self._name, + ex, + ) self._available = True From e9a6ba5c6d68d81231044ff2b2dc9fe1ad71a33a Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sat, 14 Sep 2019 02:50:02 +0000 Subject: [PATCH 07/13] Moved const to package Const.py --- homeassistant/components/template/const.py | 3 +++ homeassistant/components/template/switch.py | 2 +- homeassistant/const.py | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/template/const.py diff --git a/homeassistant/components/template/const.py b/homeassistant/components/template/const.py new file mode 100644 index 00000000000000..e6cf69341f9de8 --- /dev/null +++ b/homeassistant/components/template/const.py @@ -0,0 +1,3 @@ +"""Constants for the Template Platform Components.""" + +CONF_AVAILABILITY_TEMPLATE = "availability_template" diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index bec63fd4539745..120104c60f676e 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -14,13 +14,13 @@ CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, - CONF_AVAILABILITY_TEMPLATE, STATE_OFF, STATE_ON, ATTR_ENTITY_ID, CONF_SWITCHES, EVENT_HOMEASSISTANT_START, ) +from .const import CONF_AVAILABILITY_TEMPLATE from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id diff --git a/homeassistant/const.py b/homeassistant/const.py index 5eb351916190ba..4cfd16b8c9f0cc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -35,7 +35,6 @@ CONF_AUTHENTICATION = "authentication" CONF_AUTH_MFA_MODULES = "auth_mfa_modules" CONF_AUTH_PROVIDERS = "auth_providers" -CONF_AVAILABILITY_TEMPLATE = "availability_template" CONF_BASE = "base" CONF_BEFORE = "before" CONF_BELOW = "below" From b5d12f84e14e092ab577b6b7ead5d5ddafe30a1a Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sat, 14 Sep 2019 03:18:52 +0000 Subject: [PATCH 08/13] Fix import order (pylint) --- homeassistant/components/template/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 120104c60f676e..1e3cdd11ef8cd6 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -20,12 +20,12 @@ CONF_SWITCHES, EVENT_HOMEASSISTANT_START, ) -from .const import CONF_AVAILABILITY_TEMPLATE 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 .const import CONF_AVAILABILITY_TEMPLATE _LOGGER = logging.getLogger(__name__) _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"] From 64e520a3d8f5f2de487b6b00f9d0d18dca2aee0c Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Tue, 17 Sep 2019 02:52:29 +0000 Subject: [PATCH 09/13] Refactored availability_tempalte rendering to common loop --- homeassistant/components/template/switch.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 1e3cdd11ef8cd6..5993973c5fe8ee 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -32,6 +32,7 @@ ON_ACTION = "turn_on" OFF_ACTION = "turn_off" +EXPECTED_AVAILABILITY_RENDER_RESULT = "true" SWITCH_SCHEMA = vol.Schema( { @@ -131,7 +132,7 @@ def __init__( self._icon = None self._entity_picture = None self._entities = entity_ids - self._available = True + self._available = EXPECTED_AVAILABILITY_RENDER_RESULT async def async_added_to_hass(self): """Register callbacks.""" @@ -182,7 +183,10 @@ def entity_picture(self): @property def available(self) -> bool: """Return if the device is available.""" - return self._available + return ( + self._available is not None + and self._available == EXPECTED_AVAILABILITY_RENDER_RESULT + ) async def async_turn_on(self, **kwargs): """Fire the on action.""" @@ -214,6 +218,7 @@ async def async_update(self): for property_name, template in ( ("_icon", self._icon_template), ("_entity_picture", self._entity_picture_template), + ("_available", self._availability_template), ): if template is None: continue @@ -242,15 +247,3 @@ async def async_update(self): self._name, ex, ) - if self._availability_template is not None: - try: - result = self._availability_template.async_render() - self._available = result == "true" - except (TemplateError, ValueError) as ex: - _LOGGER.error( - "Could not render %s template %s: %s", - friendly_property_name, - self._name, - ex, - ) - self._available = True From 3b94ea3283b56219492dbe8fd2f5cae515e5009d Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Tue, 17 Sep 2019 20:19:45 +1000 Subject: [PATCH 10/13] Cleaned up const and compare lowercase result to 'true' --- homeassistant/components/template/switch.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 5993973c5fe8ee..81c5416862c2a8 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -32,7 +32,6 @@ ON_ACTION = "turn_on" OFF_ACTION = "turn_off" -EXPECTED_AVAILABILITY_RENDER_RESULT = "true" SWITCH_SCHEMA = vol.Schema( { @@ -132,7 +131,7 @@ def __init__( self._icon = None self._entity_picture = None self._entities = entity_ids - self._available = EXPECTED_AVAILABILITY_RENDER_RESULT + self._available = "true" async def async_added_to_hass(self): """Register callbacks.""" @@ -183,10 +182,7 @@ def entity_picture(self): @property def available(self) -> bool: """Return if the device is available.""" - return ( - self._available is not None - and self._available == EXPECTED_AVAILABILITY_RENDER_RESULT - ) + return self._available is not None and self._available.lower() == "true" async def async_turn_on(self, **kwargs): """Fire the on action.""" From ebbf9f055a2d6c6eed35421e2d27db380ca86ccf Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 22 Sep 2019 01:12:01 +0000 Subject: [PATCH 11/13] reverted _available back to boolean --- homeassistant/components/template/switch.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 81c5416862c2a8..2fd4074d7ecf25 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -131,7 +131,7 @@ def __init__( self._icon = None self._entity_picture = None self._entities = entity_ids - self._available = "true" + self._available = True async def async_added_to_hass(self): """Register callbacks.""" @@ -182,7 +182,7 @@ def entity_picture(self): @property def available(self) -> bool: """Return if the device is available.""" - return self._available is not None and self._available.lower() == "true" + return self._available async def async_turn_on(self, **kwargs): """Fire the on action.""" @@ -220,7 +220,10 @@ async def async_update(self): continue try: - setattr(self, property_name, template.async_render()) + value = template.async_render() + if property_name == "_available": + value = value.lower() == "true" + setattr(self, property_name, value) except TemplateError as ex: friendly_property_name = property_name[1:].replace("_", " ") if ex.args and ex.args[0].startswith( From 93a6077ac3ccf1bfd2b987e18efd08b940d252b0 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 22 Sep 2019 02:34:41 +0000 Subject: [PATCH 12/13] Fixed tests (async, magic values and state checks) --- tests/components/template/test_switch.py | 156 +++++++++++------------ 1 file changed, 73 insertions(+), 83 deletions(-) diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index 5e5ff967b67298..3adc5dcad46db9 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -135,89 +135,6 @@ def test_template_state_boolean_off(self): state = self.hass.states.get("switch.test_template_switch") assert state.state == STATE_OFF - def test_available_template_with_entities(self): - """Test availability templates with values from other entities.""" - availability_template = """ - {% if is_state('availability_boolean.state', 'True') %} - {{ 'true' }} - {% else %} - {{ 'false' }} - {% endif %} - """ - with assert_setup_component(1, "switch"): - assert setup.setup_component( - self.hass, - "switch", - { - "switch": { - "platform": "template", - "switches": { - "test_template_switch": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, - "availability_template": availability_template, - } - }, - } - }, - ) - - self.hass.start() - self.hass.block_till_done() - - self.hass.states.set("availability_boolean.state", True) - self.hass.block_till_done() - - state = self.hass.states.get("switch.test_template_switch") - assert state.state != STATE_UNAVAILABLE - - self.hass.states.set("availability_boolean.state", False) - self.hass.block_till_done() - - state = self.hass.states.get("switch.test_template_switch") - assert state.state == STATE_UNAVAILABLE - - def test_invalid_availability_template_keeps_component_available(self, caplog): - """Test that an invalid availability keeps the device available.""" - with assert_setup_component(1, "switch"): - assert setup.setup_component( - self.hass, - "switch", - { - "switch": { - "platform": "template", - "switches": { - "test_template_switch": { - "value_template": "{{ true }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, - "availability_template": "{{ x - 12 }}", - } - }, - } - }, - ) - - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("switch.test_template_switch") - assert state.state != STATE_UNAVAILABLE - assert ("UndefinedError: 'x' is undefined") in caplog.text - def test_icon_template(self): """Test icon template.""" with assert_setup_component(1, "switch"): @@ -557,3 +474,76 @@ def test_off_action(self): self.hass.block_till_done() assert len(self.calls) == 1 + + +async def test_available_template_with_entities(hass): + """Test availability templates with values from other entities.""" + await setup.async_setup_component( + hass, + "switch", + { + "switch": { + "platform": "template", + "switches": { + "test_template_switch": { + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "switch.turn_on", + "entity_id": "switch.test_state", + }, + "turn_off": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + "availability_template": "{{ is_state('availability_state.state', 'on') }}", + } + }, + } + }, + ) + + await hass.async_start() + await hass.async_block_till_done() + + hass.states.async_set("availability_state.state", STATE_ON) + await hass.async_block_till_done() + + assert hass.states.get("switch.test_template_switch").state != STATE_UNAVAILABLE + + hass.states.async_set("availability_state.state", STATE_OFF) + await hass.async_block_till_done() + + assert hass.states.get("switch.test_template_switch").state == STATE_UNAVAILABLE + + +async def test_invalid_availability_template_keeps_component_available(hass, caplog): + """Test that an invalid availability keeps the device available.""" + await setup.async_setup_component( + hass, + "switch", + { + "switch": { + "platform": "template", + "switches": { + "test_template_switch": { + "value_template": "{{ true }}", + "turn_on": { + "service": "switch.turn_on", + "entity_id": "switch.test_state", + }, + "turn_off": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + "availability_template": "{{ x - 12 }}", + } + }, + } + }, + ) + + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.get("switch.test_template_switch").state != STATE_UNAVAILABLE + assert ("UndefinedError: 'x' is undefined") in caplog.text From b576d5990cac04202779f1c3c0bc35930faa589a Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Tue, 24 Sep 2019 22:52:11 +0000 Subject: [PATCH 13/13] Fixed Enity Extraction --- homeassistant/components/template/switch.py | 48 ++++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 2fd4074d7ecf25..2d4dda032ca857 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -19,6 +19,7 @@ ATTR_ENTITY_ID, CONF_SWITCHES, EVENT_HOMEASSISTANT_START, + MATCH_ALL, ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv @@ -63,19 +64,44 @@ 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] - entity_ids = set( - device_config.get(ATTR_ENTITY_ID) or state_template.extract_entities() - ) - state_template.hass = hass - - templates = [icon_template, entity_picture_template, availability_template] - - for template in templates: + manual_entity_ids = device_config.get(ATTR_ENTITY_ID) + 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, + } + invalid_templates = [] + + for template_name, template in templates.items(): if template is not None: template.hass = hass - temp_entities = template.extract_entities() - if temp_entities is not None: - entity_ids |= set(temp_entities) + + 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 switches.append( SwitchTemplate(