From c390bed5f6bd4713f2126dc9099555baefa12740 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 11 Aug 2019 11:12:21 +0000 Subject: [PATCH 01/11] Added 'availability_template' to template cover --- homeassistant/components/template/cover.py | 29 ++++++++ homeassistant/const.py | 1 + tests/components/template/test_cover.py | 81 ++++++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 51b9a523b3bbe2..65652c7acd9400 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -25,6 +25,7 @@ CONF_ENTITY_ID, EVENT_HOMEASSISTANT_START, MATCH_ALL, + CONF_AVAILABILITY_TEMPLATE, CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_DEVICE_CLASS, @@ -74,6 +75,7 @@ vol.Exclusive( CONF_VALUE_TEMPLATE, CONF_VALUE_OR_POSITION_TEMPLATE ): cv.template, + vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Optional(CONF_POSITION_TEMPLATE): cv.template, vol.Optional(CONF_TILT_TEMPLATE): cv.template, vol.Optional(CONF_ICON_TEMPLATE): cv.template, @@ -103,6 +105,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= 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) device_class = device_config.get(CONF_DEVICE_CLASS) open_action = device_config.get(OPEN_ACTION) @@ -144,6 +147,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= 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 @@ -160,6 +168,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= tilt_template, icon_template, entity_picture_template, + availability_template, open_action, close_action, stop_action, @@ -192,6 +201,7 @@ def __init__( tilt_template, icon_template, entity_picture_template, + availability_template, open_action, close_action, stop_action, @@ -213,6 +223,7 @@ def __init__( self._icon_template = icon_template self._device_class = device_class self._entity_picture_template = entity_picture_template + self._availability_template = availability_template self._open_script = None if open_action is not None: self._open_script = Script(hass, open_action) @@ -235,6 +246,7 @@ def __init__( self._position = None self._tilt_value = None self._entities = entity_ids + self._available = True if self._template is not None: self._template.hass = self.hass @@ -246,6 +258,8 @@ def __init__( self._icon_template.hass = self.hass if self._entity_picture_template is not None: self._entity_picture_template.hass = self.hass + if self._availability_template is not None: + self._availability_template.hass = self.hass async def async_added_to_hass(self): """Register callbacks.""" @@ -332,6 +346,11 @@ def should_poll(self): """Return the polling state.""" return False + @property + def available(self) -> bool: + """Return if the device is available.""" + return self._available + async def async_open_cover(self, **kwargs): """Move the cover up.""" if self._open_script: @@ -453,6 +472,16 @@ async def async_update(self): except ValueError as ex: _LOGGER.error(ex) self._tilt_value = None + if self._availability_template is not None: + try: + result = self._availability_template.async_render() + self._available = result == "true" + except TemplateError as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True for property_name, template in ( ("_icon", self._icon_template), diff --git a/homeassistant/const.py b/homeassistant/const.py index eebd10f4fb9089..d45b29c8d33450 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_cover.py b/tests/components/template/test_cover.py index 247ee25027c94f..36208ac215ab40 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -16,6 +16,7 @@ SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER, STATE_CLOSED, + STATE_UNAVAILABLE, STATE_OPEN, ) @@ -839,6 +840,86 @@ async def test_entity_picture_template(hass, calls): assert state.attributes["entity_picture"] == "/local/cover.png" +async def test_availability_template(hass, calls): + """Test availability template.""" + with assert_setup_component(1, "cover"): + assert await setup.async_setup_component( + hass, + "cover", + { + "cover": { + "platform": "template", + "covers": { + "test_template_cover": { + "value_template": "open", + "open_cover": { + "service": "cover.open_cover", + "entity_id": "cover.test_state", + }, + "close_cover": { + "service": "cover.close_cover", + "entity_id": "cover.test_state", + }, + "availability_template": "{% if states.cover.test_state.state == 'open' %}" + "true" + "{% else %}" + "false" + "{% endif %}", + } + }, + } + }, + ) + + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.async_set("cover.test_state", STATE_CLOSED) + await hass.async_block_till_done() + + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_UNAVAILABLE + + state = hass.states.async_set("cover.test_state", STATE_OPEN) + await hass.async_block_till_done() + + state = hass.states.get("cover.test_template_cover") + assert state.state != STATE_UNAVAILABLE + + +async def test_availability_without_availability_template(hass, calls): + """Test that component is availble if there is no.""" + with assert_setup_component(1, "cover"): + assert await setup.async_setup_component( + hass, + "cover", + { + "cover": { + "platform": "template", + "covers": { + "test_template_cover": { + "value_template": "open", + "open_cover": { + "service": "cover.open_cover", + "entity_id": "cover.test_state", + }, + "close_cover": { + "service": "cover.close_cover", + "entity_id": "cover.test_state", + }, + } + }, + } + }, + ) + + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("cover.test_template_cover") + assert state.state != STATE_UNAVAILABLE + + async def test_device_class(hass, calls): """Test device class.""" with assert_setup_component(1, "cover"): From 60756ac48b3f67c82b369b0baa25c88dff2eb54a Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 11 Aug 2019 23:52:31 +0000 Subject: [PATCH 02/11] Added 'availability_template' to template binary_sensor --- .../components/template/binary_sensor.py | 24 +++++++ .../components/template/test_binary_sensor.py | 63 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index 8b354f4eeb2b57..b57089ebfd05a7 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -13,6 +13,7 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, + CONF_AVAILABILITY_TEMPLATE, CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, @@ -36,6 +37,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.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, @@ -57,6 +59,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= value_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) entity_ids = set() manual_entity_ids = device_config.get(ATTR_ENTITY_ID) @@ -66,6 +69,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= (CONF_VALUE_TEMPLATE, value_template), (CONF_ICON_TEMPLATE, icon_template), (CONF_ENTITY_PICTURE_TEMPLATE, entity_picture_template), + (CONF_AVAILABILITY_TEMPLATE, availability_template), ): if template is None: continue @@ -111,6 +115,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= value_template, icon_template, entity_picture_template, + availability_template, entity_ids, delay_on, delay_off, @@ -136,6 +141,7 @@ def __init__( value_template, icon_template, entity_picture_template, + availability_template, entity_ids, delay_on, delay_off, @@ -148,12 +154,14 @@ def __init__( self._template = value_template self._state = None self._icon_template = icon_template + self._availability_template = availability_template self._entity_picture_template = entity_picture_template self._icon = None self._entity_picture = None self._entities = entity_ids self._delay_on = delay_on self._delay_off = delay_off + self._available = True async def async_added_to_hass(self): """Register callbacks.""" @@ -208,6 +216,11 @@ def should_poll(self): """No polling needed.""" return False + @property + def available(self): + """Availability indicator.""" + return self._available + @callback def _async_render(self): """Get the state of template.""" @@ -225,6 +238,17 @@ def _async_render(self): return _LOGGER.error("Could not render template %s: %s", self._name, ex) + if self._availability_template is not None: + try: + result = self._availability_template.async_render() + self._available = result == "true" + except TemplateError as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True + for property_name, template in ( ("_icon", self._icon_template), ("_entity_picture", self._entity_picture_template), diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index c0b73f9c559623..7175eeaa9df9da 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -206,6 +206,7 @@ def test_attributes(self): template_hlpr.Template("{{ 1 > 1 }}", self.hass), None, None, + None, MATCH_ALL, None, None, @@ -265,6 +266,7 @@ def test_update_template_error(self, mock_render): template_hlpr.Template("{{ 1 > 1 }}", self.hass), None, None, + None, MATCH_ALL, None, None, @@ -394,6 +396,67 @@ async def test_template_delay_off(hass): assert state.state == "on" +async def test_available_without_availability_template(hass): + """Ensure availability is true without an availability_template""" + config = { + "binary_sensor": { + "platform": "template", + "sensors": { + "test": { + "friendly_name": "virtual thingy", + "value_template": "true", + "device_class": "motion", + "delay_off": 5, + } + }, + } + } + await setup.async_setup_component(hass, "binary_sensor", config) + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test") + assert state.state != "unavailable" + + +async def test_availability_template(hass): + """Test availability template.""" + config = { + "binary_sensor": { + "platform": "template", + "sensors": { + "test": { + "friendly_name": "virtual thingy", + "value_template": "true", + "device_class": "motion", + "delay_off": 5, + "availability_template": "{% if states.sensor.test_state.state == 'on' %}" + "true" + "{% else %}" + "false" + "{% endif %}", + } + }, + } + } + hass.states.async_set("sensor.test_state", "on") + await setup.async_setup_component(hass, "binary_sensor", config) + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.async_set("sensor.test_state", "off") + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test") + assert state.state == "unavailable" + + hass.states.async_set("sensor.test_state", "on") + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.test") + assert state.state != "unavailable" + + async def test_no_update_template_match_all(hass, caplog): """Test that we do not update sensors that match on all.""" hass.states.async_set("binary_sensor.test_sensor", "true") From c3afb0ff2f03fa8ad79e0f6dabe1e291fe351a4e Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 02:44:31 +0000 Subject: [PATCH 03/11] Added 'availability_template' to template fan --- homeassistant/components/template/fan.py | 27 ++++++++++++ tests/components/template/test_fan.py | 55 +++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index c3d5a4d878fd9e..c8acf07e15d793 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -21,6 +21,7 @@ ) from homeassistant.const import ( CONF_FRIENDLY_NAME, + CONF_AVAILABILITY_TEMPLATE, CONF_VALUE_TEMPLATE, CONF_ENTITY_ID, STATE_ON, @@ -58,6 +59,7 @@ vol.Optional(CONF_SPEED_TEMPLATE): cv.template, vol.Optional(CONF_OSCILLATING_TEMPLATE): cv.template, vol.Optional(CONF_DIRECTION_TEMPLATE): cv.template, + vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_SET_SPEED_ACTION): cv.SCRIPT_SCHEMA, @@ -86,6 +88,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= speed_template = device_config.get(CONF_SPEED_TEMPLATE) oscillating_template = device_config.get(CONF_OSCILLATING_TEMPLATE) direction_template = device_config.get(CONF_DIRECTION_TEMPLATE) + availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) on_action = device_config[CONF_ON_ACTION] off_action = device_config[CONF_OFF_ACTION] @@ -103,6 +106,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= speed_template, oscillating_template, direction_template, + availability_template, ): if template is None: continue @@ -131,6 +135,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= speed_template, oscillating_template, direction_template, + availability_template, on_action, off_action, set_speed_action, @@ -156,6 +161,7 @@ def __init__( speed_template, oscillating_template, direction_template, + availability_template, on_action, off_action, set_speed_action, @@ -175,6 +181,8 @@ def __init__( self._speed_template = speed_template self._oscillating_template = oscillating_template self._direction_template = direction_template + self._availability_template = availability_template + self._available = True self._supported_features = 0 self._on_script = Script(hass, on_action) @@ -207,6 +215,8 @@ def __init__( if self._direction_template: self._direction_template.hass = self.hass self._supported_features |= SUPPORT_DIRECTION + if self._availability_template: + self._availability_template.hass = self.hass self._entities = entity_ids # List of valid speeds @@ -252,6 +262,11 @@ def should_poll(self): """Return the polling state.""" return False + @property + def available(self): + """Return availability of Device.""" + return self._available + # pylint: disable=arguments-differ async def async_turn_on(self, speed: str = None) -> None: """Turn on the fan.""" @@ -422,3 +437,15 @@ async def async_update(self): ", ".join(_VALID_DIRECTIONS), ) self._direction = None + + # Update Availability if 'availability_template' is defined + if self._availability_template is not None: + try: + result = self._availability_template.async_render() + self._available = result == "true" + except TemplateError as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index b80522b37e24c5..992a209118dcf0 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant import setup -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE from homeassistant.components.fan import ( ATTR_SPEED, ATTR_OSCILLATING, @@ -26,6 +26,8 @@ _TEST_FAN = "fan.test_fan" # Represent for fan's state _STATE_INPUT_BOOLEAN = "input_boolean.state" +# Represent for fan's state +_STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state" # Represent for fan's speed _SPEED_INPUT_SELECT = "input_select.speed" # Represent for fan's oscillating @@ -214,6 +216,57 @@ async def test_templates_with_entities(hass, calls): _verify(hass, STATE_ON, SPEED_MEDIUM, True, DIRECTION_FORWARD) +async def test_availability_template_with_entities(hass, calls): + """Test availability tempalates with values from other entities.""" + availability_template = """ + {% if is_state('availability_boolean.state', 'True') %} + {{ 'true' }} + {% else %} + {{ 'false' }} + {% endif %} + """ + with assert_setup_component(1, "fan"): + assert await setup.async_setup_component( + hass, + "fan", + { + "fan": { + "platform": "template", + "fans": { + "test_fan": { + "availability_template": availability_template, + "value_template": "{{ 'on' }}", + "speed_template": "{{ 'medium' }}", + "oscillating_template": "{{ 1 == 1 }}", + "direction_template": "{{ 'forward' }}", + "turn_on": {"service": "script.fan_on"}, + "turn_off": {"service": "script.fan_off"}, + } + }, + } + }, + ) + + await hass.async_start() + await hass.async_block_till_done() + + # When template returns true.. + hass.states.async_set(_STATE_AVAILABILITY_BOOLEAN, True) + await hass.async_block_till_done() + + # Device State should not be unavailable + state = hass.states.get(_TEST_FAN) + assert state.state != STATE_UNAVAILABLE + + # When Availability template returns false + hass.states.async_set(_STATE_AVAILABILITY_BOOLEAN, False) + await hass.async_block_till_done() + + # device state should be unavailable + state = hass.states.get(_TEST_FAN) + assert state.state == STATE_UNAVAILABLE + + async def test_templates_with_valid_values(hass, calls): """Test templates with valid values.""" with assert_setup_component(1, "fan"): From 87f037f62b98a7e00883e5968b3fb24e22dd6350 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 03:59:53 +0000 Subject: [PATCH 04/11] Added 'availability_template' to template light --- homeassistant/components/template/light.py | 35 +++++++++++- tests/components/template/test_light.py | 62 +++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 320dcd2e22feb1..04bc9a8d87f80c 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -14,6 +14,7 @@ CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, + CONF_AVAILABILITY_TEMPLATE, CONF_ENTITY_ID, CONF_FRIENDLY_NAME, STATE_ON, @@ -44,6 +45,7 @@ vol.Optional(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.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_LEVEL_TEMPLATE): cv.template, vol.Optional(CONF_FRIENDLY_NAME): cv.string, @@ -65,6 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= 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) on_action = device_config[CONF_ON_ACTION] off_action = device_config[CONF_OFF_ACTION] level_action = device_config.get(CONF_LEVEL_ACTION) @@ -92,6 +95,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= 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 @@ -105,6 +113,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, level_action, @@ -132,6 +141,7 @@ def __init__( state_template, icon_template, entity_picture_template, + availability_template, on_action, off_action, level_action, @@ -147,6 +157,7 @@ def __init__( self._template = state_template self._icon_template = icon_template self._entity_picture_template = entity_picture_template + self._availability_template = availability_template self._on_script = Script(hass, on_action) self._off_script = Script(hass, off_action) self._level_script = None @@ -159,6 +170,7 @@ def __init__( self._entity_picture = None self._brightness = None self._entities = entity_ids + self._available = True if self._template is not None: self._template.hass = self.hass @@ -168,6 +180,8 @@ def __init__( self._icon_template.hass = self.hass if self._entity_picture_template is not None: self._entity_picture_template.hass = self.hass + if self._availability_template is not None: + self._availability_template.hass = self.hass @property def brightness(self): @@ -207,6 +221,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_added_to_hass(self): """Register callbacks.""" @@ -218,7 +237,11 @@ def template_light_state_listener(entity, old_state, new_state): @callback def template_light_startup(event): """Update template on startup.""" - if self._template is not None or self._level_template is not None: + if ( + self._template is not None + or self._level_template is not None + or self._availability_template is not None + ): async_track_state_change( self.hass, self._entities, template_light_state_listener ) @@ -326,3 +349,13 @@ 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 as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 87fd8cd4db3f34..bf0c6bf3689108 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -4,13 +4,16 @@ from homeassistant.core import callback from homeassistant import setup from homeassistant.components.light import ATTR_BRIGHTNESS -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.light import common _LOGGER = logging.getLogger(__name__) +# Represent for light's availability +_STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state" + class TestTemplateLight: """Test the Template light.""" @@ -774,3 +777,60 @@ def test_entity_picture_template(self): state = self.hass.states.get("light.test_template_light") assert state.attributes["entity_picture"] == "/local/light.png" + + def test_available_template_with_entities(self): + """Test availability tempalates with values from other entities.""" + availability_template = """ + {% if is_state('availability_boolean.state', 'True') %} + {{ 'true' }} + {% else %} + {{ 'false' }} + {% endif %} + """ + assert setup.setup_component( + self.hass, + "light", + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "availability_template": availability_template, + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + } + }, + } + }, + ) + self.hass.start() + self.hass.block_till_done() + + # When template returns true.. + self.hass.states.set(_STATE_AVAILABILITY_BOOLEAN, True) + self.hass.block_till_done() + + # Device State should not be unavailable + state = self.hass.states.get("light.test_template_light") + assert state.state != STATE_UNAVAILABLE + + # When Availability template returns false + self.hass.states.set(_STATE_AVAILABILITY_BOOLEAN, False) + self.hass.block_till_done() + + # device state should be unavailable + state = self.hass.states.get("light.test_template_light") + assert state.state == STATE_UNAVAILABLE From 4de6e15809814adc152ee35ed21ba2b778434e73 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 04:43:28 +0000 Subject: [PATCH 05/11] Added 'availability_template' to template lock --- homeassistant/components/template/lock.py | 38 +++++++++++++++-- tests/components/template/test_lock.py | 51 ++++++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index d7f501987f9527..098d55361cdbae 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -11,6 +11,7 @@ CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, + CONF_AVAILABILITY_TEMPLATE, EVENT_HOMEASSISTANT_START, STATE_ON, STATE_LOCKED, @@ -34,6 +35,7 @@ vol.Required(CONF_LOCK): cv.SCRIPT_SCHEMA, vol.Required(CONF_UNLOCK): cv.SCRIPT_SCHEMA, vol.Required(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, } ) @@ -48,21 +50,32 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N 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 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) + async_add_devices( [ TemplateLock( hass, name, value_template, - value_template_entity_ids, + availability_template, + template_entity_ids, config.get(CONF_LOCK), config.get(CONF_UNLOCK), config.get(CONF_OPTIMISTIC), @@ -79,6 +92,7 @@ def __init__( hass, name, value_template, + availability_template, entity_ids, command_lock, command_unlock, @@ -89,10 +103,12 @@ def __init__( self._hass = hass self._name = name self._state_template = value_template + self._availability_template = availability_template self._state_entities = entity_ids self._command_lock = Script(hass, command_lock) self._command_unlock = Script(hass, command_unlock) self._optimistic = optimistic + self._available = True async def async_added_to_hass(self): """Register callbacks.""" @@ -136,6 +152,11 @@ def is_locked(self): """Return true if lock is locked.""" return self._state + @property + def available(self) -> bool: + """Return if the device is available.""" + return self._available + async def async_update(self): """Update the state from the template.""" try: @@ -148,6 +169,17 @@ async def async_update(self): self._state = None _LOGGER.error("Could not render template %s: %s", self._name, ex) + if self._availability_template is not None: + try: + result = self._availability_template.async_render() + self._available = result == "true" + except TemplateError as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True + async def async_lock(self, **kwargs): """Lock the device.""" if self._optimistic: diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 24cde24051a69a..ab5da31457739b 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -5,7 +5,7 @@ from homeassistant import setup from homeassistant.components import lock from homeassistant.const import ATTR_ENTITY_ID -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 @@ -332,3 +332,52 @@ def test_unlock_action(self): self.hass.block_till_done() assert len(self.calls) == 1 + + 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 %} + """ + + assert setup.setup_component( + self.hass, + "lock", + { + "lock": { + "platform": "template", + "value_template": "{{ 'on' }}", + "lock": { + "service": "switch.turn_on", + "entity_id": "switch.test_state", + }, + "unlock": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + "availability_template": availability_template, + } + }, + ) + + self.hass.start() + self.hass.block_till_done() + + # When template returns true.. + self.hass.states.set("availability_boolean.state", True) + self.hass.block_till_done() + + # Device State should not be unavailable + state = self.hass.states.get("lock.template_lock") + assert state.state != STATE_UNAVAILABLE + + # When Availability template returns false + self.hass.states.set("availability_boolean.state", False) + self.hass.block_till_done() + + # device state should be unavailable + state = self.hass.states.get("lock.template_lock") + assert state.state == STATE_UNAVAILABLE From 8f573727ffcb01dcc7b2347b7be5aeb4996fe179 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 05:10:16 +0000 Subject: [PATCH 06/11] Added 'availability_template' to template sensor --- homeassistant/components/template/sensor.py | 23 ++++++++++ tests/components/template/test_sensor.py | 47 +++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index a5397e0ea7d353..86b1afbeef9b4b 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -16,6 +16,7 @@ CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, + CONF_AVAILABILITY_TEMPLATE, ATTR_ENTITY_ID, CONF_SENSORS, EVENT_HOMEASSISTANT_START, @@ -36,6 +37,7 @@ vol.Optional(CONF_ICON_TEMPLATE): cv.template, vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template, vol.Optional(CONF_FRIENDLY_NAME_TEMPLATE): cv.template, + vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, @@ -56,6 +58,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) friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) friendly_name_template = device_config.get(CONF_FRIENDLY_NAME_TEMPLATE) unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT) @@ -70,6 +73,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= (CONF_ICON_TEMPLATE, icon_template), (CONF_ENTITY_PICTURE_TEMPLATE, entity_picture_template), (CONF_FRIENDLY_NAME_TEMPLATE, friendly_name_template), + (CONF_AVAILABILITY_TEMPLATE, availability_template), ): if template is None: continue @@ -111,6 +115,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= state_template, icon_template, entity_picture_template, + availability_template, entity_ids, device_class, ) @@ -136,6 +141,7 @@ def __init__( state_template, icon_template, entity_picture_template, + availability_template, entity_ids, device_class, ): @@ -151,10 +157,12 @@ def __init__( self._state = None 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._device_class = device_class + self._available = True async def async_added_to_hass(self): """Register callbacks.""" @@ -209,6 +217,11 @@ def unit_of_measurement(self): """Return the unit_of_measurement of the device.""" return self._unit_of_measurement + @property + def available(self) -> bool: + """Return if the device is available.""" + return self._available + @property def should_poll(self): """No polling needed.""" @@ -261,3 +274,13 @@ 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 as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 298efc6bebbd43..fc82fa4e54b809 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -3,6 +3,7 @@ from homeassistant.setup import setup_component, async_setup_component from tests.common import get_test_home_assistant, assert_setup_component +from homeassistant.const import STATE_UNAVAILABLE class TestTemplateSensor: @@ -344,6 +345,52 @@ def test_setup_valid_device_class(self): 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.""" + availability_template = """ + {% if is_state('availability_boolean.state', 'True') %} + {{ 'true' }} + {% else %} + {{ 'false' }} + {% endif %} + """ + + 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": availability_template, + } + }, + } + }, + ) + + self.hass.start() + self.hass.block_till_done() + + # When template returns true.. + self.hass.states.set("availability_boolean.state", True) + self.hass.block_till_done() + + # Device State should not be unavailable + state = self.hass.states.get("sensor.test_template_sensor") + assert state.state != STATE_UNAVAILABLE + + # When Availability template returns false + self.hass.states.set("availability_boolean.state", False) + self.hass.block_till_done() + + # device state should be unavailable + state = self.hass.states.get("sensor.test_template_sensor") + assert state.state == STATE_UNAVAILABLE + async def test_no_template_match_all(hass, caplog): """Test that we do not allow sensors that match on all.""" From 8f634c5b99d0f6e156012d1c60fa0648054776f4 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 05:35:53 +0000 Subject: [PATCH 07/11] Added 'availability_template' to template switch --- homeassistant/components/template/switch.py | 29 ++++++++++++ tests/components/template/test_switch.py | 51 ++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index c77a90c1f8b6ee..55a5333af64e52 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.""" @@ -175,6 +189,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 +252,13 @@ 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 as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True 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 14453604b1d860de50ec4f48ea8dd352b2b69334 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 06:38:01 +0000 Subject: [PATCH 08/11] Added 'availability_template' to template vacuum --- homeassistant/components/template/vacuum.py | 24 +++++++++++ tests/components/template/test_vacuum.py | 47 ++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 5374247daccd70..bc254d6de959e2 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -34,6 +34,7 @@ from homeassistant.const import ( CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, + CONF_AVAILABILITY_TEMPLATE, CONF_ENTITY_ID, MATCH_ALL, EVENT_HOMEASSISTANT_START, @@ -67,6 +68,7 @@ vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_BATTERY_LEVEL_TEMPLATE): cv.template, vol.Optional(CONF_FAN_SPEED_TEMPLATE): cv.template, + vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Required(SERVICE_START): cv.SCRIPT_SCHEMA, vol.Optional(SERVICE_PAUSE): cv.SCRIPT_SCHEMA, vol.Optional(SERVICE_STOP): cv.SCRIPT_SCHEMA, @@ -94,6 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= state_template = device_config.get(CONF_VALUE_TEMPLATE) battery_level_template = device_config.get(CONF_BATTERY_LEVEL_TEMPLATE) fan_speed_template = device_config.get(CONF_FAN_SPEED_TEMPLATE) + availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) start_action = device_config[SERVICE_START] pause_action = device_config.get(SERVICE_PAUSE) @@ -113,6 +116,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= (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 @@ -152,6 +156,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= state_template, battery_level_template, fan_speed_template, + availability_template, start_action, pause_action, stop_action, @@ -178,6 +183,7 @@ def __init__( state_template, battery_level_template, fan_speed_template, + availability_template, start_action, pause_action, stop_action, @@ -198,6 +204,7 @@ def __init__( self._template = state_template self._battery_level_template = battery_level_template self._fan_speed_template = fan_speed_template + self._availability_template = availability_template self._supported_features = SUPPORT_START self._start_script = Script(hass, start_action) @@ -235,6 +242,7 @@ def __init__( self._state = None self._battery_level = None self._fan_speed = None + self._available = True if self._template: self._supported_features |= SUPPORT_STATE @@ -280,6 +288,11 @@ def should_poll(self): """Return the polling state.""" return False + @property + def available(self) -> bool: + """Return if the device is available.""" + return self._available + async def async_start(self): """Start or resume the cleaning task.""" await self._start_script.async_run(context=self._context) @@ -421,3 +434,14 @@ async def async_update(self): self._fan_speed_list, ) self._fan_speed = None + # Update availability if availability template is defined + if self._availability_template is not None: + try: + result = self._availability_template.async_render() + self._available = result == "true" + except TemplateError as ex: + _LOGGER.error(ex) + self._available = True + except ValueError as ex: + _LOGGER.error(ex) + self._available = True diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index 9e3c535f136d3c..45fd0b4caf4c2a 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -3,7 +3,7 @@ import pytest from homeassistant import setup -from homeassistant.const import STATE_ON, STATE_UNKNOWN +from homeassistant.const import STATE_ON, STATE_UNKNOWN, STATE_UNAVAILABLE from homeassistant.components.vacuum import ( ATTR_BATTERY_LEVEL, STATE_CLEANING, @@ -210,6 +210,51 @@ async def test_invalid_templates(hass, calls): _verify(hass, STATE_UNKNOWN, None) +async def test_available_template_with_entities(hass, calls): + """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, "vacuum"): + assert await setup.async_setup_component( + hass, + "vacuum", + { + "vacuum": { + "platform": "template", + "vacuums": { + "test_template_vacuum": { + "availability_template": availability_template, + "start": {"service": "script.vacuum_start"}, + } + }, + } + }, + ) + await hass.async_start() + await hass.async_block_till_done() + + # When template returns true.. + hass.states.async_set("availability_boolean.state", True) + await hass.async_block_till_done() + + # Device State should not be unavailable + state = hass.states.get("vacuum.test_template_vacuum") + assert state.state != STATE_UNAVAILABLE + + # When Availability template returns false + hass.states.async_set("availability_boolean.state", False) + await hass.async_block_till_done() + + # device state should be unavailable + state = hass.states.get("vacuum.test_template_vacuum") + assert state.state == STATE_UNAVAILABLE + + # End of template tests # From d0dfdb0126456db7d7bd8c24c0d32c81c87cdf4b Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 11:30:15 +0000 Subject: [PATCH 09/11] Additional changes after pylint --- homeassistant/components/template/switch.py | 5 ----- tests/components/template/test_binary_sensor.py | 2 +- tests/components/template/test_light.py | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 55a5333af64e52..2e3624c78ee97e 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -174,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.""" diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 7175eeaa9df9da..85753fde03dfce 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -397,7 +397,7 @@ async def test_template_delay_off(hass): async def test_available_without_availability_template(hass): - """Ensure availability is true without an availability_template""" + """Ensure availability is true without an availability_template.""" config = { "binary_sensor": { "platform": "template", diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index bf0c6bf3689108..aeb93d75630540 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -779,7 +779,7 @@ def test_entity_picture_template(self): assert state.attributes["entity_picture"] == "/local/light.png" def test_available_template_with_entities(self): - """Test availability tempalates with values from other entities.""" + """Test availability templates with values from other entities.""" availability_template = """ {% if is_state('availability_boolean.state', 'True') %} {{ 'true' }} From 53a0da5258b85062522f9a9d226f8bf2b4054c33 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Mon, 12 Aug 2019 22:22:57 +0000 Subject: [PATCH 10/11] Cleaned Exception Handling and fixed failing Test --- .../components/template/binary_sensor.py | 7 ++----- homeassistant/components/template/cover.py | 21 ++++++------------- homeassistant/components/template/fan.py | 7 ++----- homeassistant/components/template/light.py | 7 ++----- homeassistant/components/template/lock.py | 7 ++----- homeassistant/components/template/sensor.py | 7 ++----- homeassistant/components/template/switch.py | 7 ++----- homeassistant/components/template/vacuum.py | 7 ++----- tests/components/template/test_lock.py | 4 ++-- 9 files changed, 22 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index b57089ebfd05a7..ca0c930e2a7874 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -242,11 +242,8 @@ def _async_render(self): try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True for property_name, template in ( diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 65652c7acd9400..31c39fcb6e456e 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -449,11 +449,8 @@ async def async_update(self): ) else: self._position = state - except TemplateError as ex: - _LOGGER.error(ex) - self._position = None - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._position = None if self._tilt_template is not None: try: @@ -466,21 +463,15 @@ async def async_update(self): ) else: self._tilt_value = state - except TemplateError as ex: - _LOGGER.error(ex) - self._tilt_value = None - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._tilt_value = None if self._availability_template is not None: try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True for property_name, template in ( diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index c8acf07e15d793..a9c16f9624c352 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -443,9 +443,6 @@ async def async_update(self): try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 04bc9a8d87f80c..554d9a7527eb5f 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -353,9 +353,6 @@ async def async_update(self): try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 098d55361cdbae..b2cc44a8aedfcf 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -173,11 +173,8 @@ async def async_update(self): try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True async def async_lock(self, **kwargs): diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 86b1afbeef9b4b..d0d67584290468 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -278,9 +278,6 @@ async def async_update(self): try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 2e3624c78ee97e..578b0ae966b4ba 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -251,9 +251,6 @@ async def async_update(self): try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index bc254d6de959e2..ea9ebc59e13cd8 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -439,9 +439,6 @@ async def async_update(self): try: result = self._availability_template.async_render() self._available = result == "true" - except TemplateError as ex: - _LOGGER.error(ex) - self._available = True - except ValueError as ex: - _LOGGER.error(ex) + except (TemplateError, ValueError) as err: + _LOGGER.error(err) self._available = True diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index ab5da31457739b..347933574f65a4 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -254,9 +254,9 @@ def test_no_template_match_all(self, caplog): assert state.state == lock.STATE_UNLOCKED assert ( - "Template lock Template Lock has no entity ids configured " + "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 " + "from the 'value_template' template. This entity will only " "be able to be updated manually." ) in caplog.text From 9193e279222ce0904f325c897974518ac5dc2b17 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 8 Sep 2019 03:49:32 +0000 Subject: [PATCH 11/11] Fixed case where availability_template is not defined --- .../components/template/binary_sensor.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index 67579965992b39..ea0efdd0559324 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -253,20 +253,22 @@ def _async_render(self): return _LOGGER.error("Could not render template %s: %s", self._name, ex) - try: - self._available = ( - self._availability_template.async_render().lower() == "true" - ) - except TemplateError as ex: - if ex.args and ex.args[0].startswith( - "UndefinedError: 'None' has no attribute" - ): - # Common during HA startup - so just a warning - _LOGGER.warning( - "Could not render template %s, " "the state is unknown", self._name + if self._availability_template is not None: + try: + self._available = ( + self._availability_template.async_render().lower() == "true" ) - return - _LOGGER.error("Could not render template %s: %s", self._name, ex) + except TemplateError as ex: + if ex.args and ex.args[0].startswith( + "UndefinedError: 'None' has no attribute" + ): + # Common during HA startup - so just a warning + _LOGGER.warning( + "Could not render template %s, " "the state is unknown", + self._name, + ) + return + _LOGGER.error("Could not render template %s: %s", self._name, ex) attrs = {} if self._attribute_templates is not None: