From a1dac28e4b2110fdf5b09ea8ff87dbee56a8ea83 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Oct 2018 13:49:15 +0200 Subject: [PATCH] Template sensors to not track all state changes (#17276) * Disable template sensor match all * Only manual update template sensors that match all --- homeassistant/components/sensor/template.py | 26 +++++- tests/components/sensor/test_template.py | 92 +++++++++++++++++++-- 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 77b3759d5fc..dbe92c2b3ba 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -57,22 +57,36 @@ async def async_setup_platform(hass, config, async_add_entities, entity_ids = set() manual_entity_ids = device_config.get(ATTR_ENTITY_ID) + invalid_templates = [] - for template in (state_template, icon_template, - entity_picture_template, friendly_name_template): + for tpl_name, template in ( + (CONF_VALUE_TEMPLATE, state_template), + (CONF_ICON_TEMPLATE, icon_template), + (CONF_ENTITY_PICTURE_TEMPLATE, entity_picture_template), + (CONF_FRIENDLY_NAME_TEMPLATE, friendly_name_template), + ): if template is None: continue template.hass = hass - if entity_ids == MATCH_ALL or manual_entity_ids is not None: + if manual_entity_ids is not None: continue template_entity_ids = template.extract_entities() if template_entity_ids == MATCH_ALL: entity_ids = MATCH_ALL - else: + # 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 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: @@ -123,6 +137,10 @@ class SensorTemplate(Entity): async def async_added_to_hass(self): """Register callbacks.""" + # We don't render on every update + if self._entities == MATCH_ALL: + return + @callback def template_sensor_state_listener(entity, old_state, new_state): """Handle device state changes.""" diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py index 2211f092d7b..159a5c34d37 100644 --- a/tests/components/sensor/test_template.py +++ b/tests/components/sensor/test_template.py @@ -1,5 +1,5 @@ """The test for the Template sensor platform.""" -from homeassistant.setup import setup_component +from homeassistant.setup import setup_component, async_setup_component from tests.common import get_test_home_assistant, assert_setup_component @@ -52,7 +52,8 @@ class TestTemplateSensor: 'platform': 'template', 'sensors': { 'test_template_sensor': { - 'value_template': "State", + 'value_template': + "{{ states.sensor.test_state.state }}", 'icon_template': "{% if states.sensor.test_state.state == " "'Works' %}" @@ -82,7 +83,8 @@ class TestTemplateSensor: 'platform': 'template', 'sensors': { 'test_template_sensor': { - 'value_template': "State", + 'value_template': + "{{ states.sensor.test_state.state }}", 'entity_picture_template': "{% if states.sensor.test_state.state == " "'Works' %}" @@ -112,7 +114,8 @@ class TestTemplateSensor: 'platform': 'template', 'sensors': { 'test_template_sensor': { - 'value_template': "State", + 'value_template': + "{{ states.sensor.test_state.state }}", 'friendly_name_template': "It {{ states.sensor.test_state.state }}." } @@ -276,7 +279,8 @@ class TestTemplateSensor: 'platform': 'template', 'sensors': { 'test': { - 'value_template': '{{ foo }}', + 'value_template': + '{{ states.sensor.test_sensor.state }}', 'device_class': 'foobarnotreal', }, }, @@ -291,10 +295,14 @@ class TestTemplateSensor: 'platform': 'template', 'sensors': { 'test1': { - 'value_template': '{{ foo }}', + 'value_template': + '{{ states.sensor.test_sensor.state }}', 'device_class': 'temperature', }, - 'test2': {'value_template': '{{ foo }}'}, + 'test2': { + 'value_template': + '{{ states.sensor.test_sensor.state }}' + }, } } }) @@ -304,3 +312,73 @@ class TestTemplateSensor: assert state.attributes['device_class'] == 'temperature' state = self.hass.states.get('sensor.test2') assert 'device_class' not in state.attributes + + +async def test_no_template_match_all(hass, caplog): + """Test that we do not allow sensors that match on all.""" + await async_setup_component(hass, 'sensor', { + 'sensor': { + 'platform': 'template', + 'sensors': { + 'invalid_state': { + 'value_template': '{{ 1 + 1 }}', + }, + 'invalid_icon': { + 'value_template': + '{{ states.sensor.test_sensor.state }}', + 'icon_template': '{{ 1 + 1 }}', + }, + 'invalid_entity_picture': { + 'value_template': + '{{ states.sensor.test_sensor.state }}', + 'entity_picture_template': '{{ 1 + 1 }}', + }, + 'invalid_friendly_name': { + 'value_template': + '{{ states.sensor.test_sensor.state }}', + 'friendly_name_template': '{{ 1 + 1 }}', + }, + } + } + }) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 4 + assert ('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 ' + '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 ' + '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 ' + 'configured to track nor were we able to extract the entities to ' + 'track from the friendly_name template') in caplog.text + + assert hass.states.get('sensor.invalid_state').state == 'unknown' + assert hass.states.get('sensor.invalid_icon').state == 'unknown' + assert hass.states.get('sensor.invalid_entity_picture').state == 'unknown' + assert hass.states.get('sensor.invalid_friendly_name').state == 'unknown' + + hass.states.async_set('sensor.test_sensor', 'hello') + await hass.async_block_till_done() + + assert hass.states.get('sensor.invalid_state').state == 'unknown' + assert hass.states.get('sensor.invalid_icon').state == 'unknown' + assert hass.states.get('sensor.invalid_entity_picture').state == 'unknown' + assert hass.states.get('sensor.invalid_friendly_name').state == 'unknown' + + await hass.helpers.entity_component.async_update_entity( + 'sensor.invalid_state') + await hass.helpers.entity_component.async_update_entity( + 'sensor.invalid_icon') + await hass.helpers.entity_component.async_update_entity( + 'sensor.invalid_entity_picture') + await hass.helpers.entity_component.async_update_entity( + 'sensor.invalid_friendly_name') + + assert hass.states.get('sensor.invalid_state').state == '2' + assert hass.states.get('sensor.invalid_icon').state == 'hello' + assert hass.states.get('sensor.invalid_entity_picture').state == 'hello' + assert hass.states.get('sensor.invalid_friendly_name').state == 'hello'