From cbde78c6cb78e46d0455cb61f839b92c86b23e14 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 8 Sep 2019 08:21:14 +0000 Subject: [PATCH 1/8] Added availability_template to Template Vacuum platform --- homeassistant/components/template/vacuum.py | 21 +++++++++ homeassistant/const.py | 1 + tests/components/template/test_vacuum.py | 47 ++++++++++++++++++++- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 5374247daccd70..ea9ebc59e13cd8 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,11 @@ 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, 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_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 ecc40aef20be639ce87df0ae281884d393c1ffba Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 8 Sep 2019 23:17:15 +0000 Subject: [PATCH 2/8] Added to test for invalid values in availability_template --- tests/components/template/test_vacuum.py | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index 45fd0b4caf4c2a..d8fbbf9f783c9e 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -255,6 +255,32 @@ async def test_available_template_with_entities(hass, calls): assert state.state == STATE_UNAVAILABLE +async def test_invalid_availability_template_keeps_component_available(hass, caplog): + """Test that an invalid availability keeps the device available.""" + with assert_setup_component(1, "vacuum"): + assert await setup.async_setup_component( + hass, + "vacuum", + { + "vacuum": { + "platform": "template", + "vacuums": { + "test_template_vacuum": { + "availability_template": "{{ x - 12 }}", + "start": {"service": "script.vacuum_start"}, + } + }, + } + }, + ) + + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.get("vacuum.test_template_vacuum") != STATE_UNAVAILABLE + assert ("UndefinedError: 'x' is undefined") in caplog.text + + # End of template tests # From 1d43c7acf315884c7c765ea96d2e0a87646e0dae Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Fri, 13 Sep 2019 00:17:13 +0000 Subject: [PATCH 3/8] Updated AVAILABILITY_TEMPLATE Rendering error --- homeassistant/components/template/vacuum.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index ea9ebc59e13cd8..6317928d2a1f25 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -439,6 +439,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", + CONF_AVAILABILITY_TEMPLATE, + self._name, + ex, + ) self._available = True From af2dc6a4a9d37a66b0364640386b66abb826747c Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sat, 14 Sep 2019 02:55:51 +0000 Subject: [PATCH 4/8] Moved const to package Const.py --- homeassistant/components/template/const.py | 3 +++ homeassistant/components/template/vacuum.py | 3 ++- homeassistant/const.py | 1 - 3 files changed, 5 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/vacuum.py b/homeassistant/components/template/vacuum.py index 6317928d2a1f25..754080074f3b5c 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -34,7 +34,6 @@ from homeassistant.const import ( CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, - CONF_AVAILABILITY_TEMPLATE, CONF_ENTITY_ID, MATCH_ALL, EVENT_HOMEASSISTANT_START, @@ -45,6 +44,8 @@ from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script +from .const import CONF_AVAILABILITY_TEMPLATE + _LOGGER = logging.getLogger(__name__) CONF_VACUUMS = "vacuums" 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 7ceef411676d0ee55888e6e7ce09550226581435 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Tue, 17 Sep 2019 03:05:29 +0000 Subject: [PATCH 5/8] Removed 'Magic' string --- homeassistant/components/template/vacuum.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 754080074f3b5c..b70bd5b6dd4c5c 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -52,6 +52,7 @@ CONF_BATTERY_LEVEL_TEMPLATE = "battery_level_template" CONF_FAN_SPEED_LIST = "fan_speeds" CONF_FAN_SPEED_TEMPLATE = "fan_speed_template" +EXPECTED_AVAILABILITY_RENDER_RESULT = "true" ENTITY_ID_FORMAT = DOMAIN + ".{}" _VALID_STATES = [ @@ -243,7 +244,7 @@ def __init__( self._state = None self._battery_level = None self._fan_speed = None - self._available = True + self._available = EXPECTED_AVAILABILITY_RENDER_RESULT if self._template: self._supported_features |= SUPPORT_STATE @@ -292,7 +293,10 @@ def should_poll(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_start(self): """Start or resume the cleaning task.""" @@ -438,8 +442,7 @@ async def async_update(self): # 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" + self._available = self._availability_template.async_render() except (TemplateError, ValueError) as ex: _LOGGER.error( "Could not render %s template %s: %s", From 04f457c243078f67939ad725c1713950a0741b17 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Tue, 17 Sep 2019 20:03:04 +1000 Subject: [PATCH 6/8] Cleaned up const and compare lowercase result to 'true' --- homeassistant/components/template/vacuum.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index b70bd5b6dd4c5c..bdf4e8ddd85403 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -52,7 +52,6 @@ CONF_BATTERY_LEVEL_TEMPLATE = "battery_level_template" CONF_FAN_SPEED_LIST = "fan_speeds" CONF_FAN_SPEED_TEMPLATE = "fan_speed_template" -EXPECTED_AVAILABILITY_RENDER_RESULT = "true" ENTITY_ID_FORMAT = DOMAIN + ".{}" _VALID_STATES = [ @@ -244,7 +243,7 @@ def __init__( self._state = None self._battery_level = None self._fan_speed = None - self._available = EXPECTED_AVAILABILITY_RENDER_RESULT + self._available = "true" if self._template: self._supported_features |= SUPPORT_STATE @@ -293,10 +292,7 @@ def should_poll(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_start(self): """Start or resume the cleaning task.""" From 34a1b72dc705d3245340e92ba68f50f14ac120dc Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 22 Sep 2019 01:15:45 +0000 Subject: [PATCH 7/8] reverted _available back to boolean --- homeassistant/components/template/vacuum.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index bdf4e8ddd85403..6a6523514c489a 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -243,7 +243,7 @@ def __init__( self._state = None self._battery_level = None self._fan_speed = None - self._available = "true" + self._available = True if self._template: self._supported_features |= SUPPORT_STATE @@ -292,7 +292,7 @@ def should_poll(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_start(self): """Start or resume the cleaning task.""" @@ -438,7 +438,9 @@ async def async_update(self): # Update availability if availability template is defined if self._availability_template is not None: try: - self._available = self._availability_template.async_render() + self._available = ( + self._availability_template.async_render().lower() == "true" + ) except (TemplateError, ValueError) as ex: _LOGGER.error( "Could not render %s template %s: %s", @@ -446,4 +448,3 @@ async def async_update(self): self._name, ex, ) - self._available = True From e6c077af55a299dd2971cf3fd0de726d5a643511 Mon Sep 17 00:00:00 2001 From: Gil Peeters Date: Sun, 22 Sep 2019 02:52:14 +0000 Subject: [PATCH 8/8] Fixed tests (async, magic values and state checks) --- tests/components/template/test_vacuum.py | 83 +++++++++++------------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index d8fbbf9f783c9e..da0e8e59ededb8 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, STATE_UNAVAILABLE +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNKNOWN, STATE_UNAVAILABLE from homeassistant.components.vacuum import ( ATTR_BATTERY_LEVEL, STATE_CLEANING, @@ -212,67 +212,58 @@ async def test_invalid_templates(hass, calls): 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"}, - } - }, - } - }, - ) + + assert await setup.async_setup_component( + hass, + "vacuum", + { + "vacuum": { + "platform": "template", + "vacuums": { + "test_template_vacuum": { + "availability_template": "{{ is_state('availability_state.state', 'on') }}", + "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) + hass.states.async_set("availability_state.state", STATE_ON) 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 + assert hass.states.get("vacuum.test_template_vacuum").state != STATE_UNAVAILABLE # When Availability template returns false - hass.states.async_set("availability_boolean.state", False) + hass.states.async_set("availability_state.state", STATE_OFF) await hass.async_block_till_done() # device state should be unavailable - state = hass.states.get("vacuum.test_template_vacuum") - assert state.state == STATE_UNAVAILABLE + assert hass.states.get("vacuum.test_template_vacuum").state == STATE_UNAVAILABLE async def test_invalid_availability_template_keeps_component_available(hass, caplog): """Test that an invalid availability keeps the device available.""" - with assert_setup_component(1, "vacuum"): - assert await setup.async_setup_component( - hass, - "vacuum", - { - "vacuum": { - "platform": "template", - "vacuums": { - "test_template_vacuum": { - "availability_template": "{{ x - 12 }}", - "start": {"service": "script.vacuum_start"}, - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + "vacuum", + { + "vacuum": { + "platform": "template", + "vacuums": { + "test_template_vacuum": { + "availability_template": "{{ x - 12 }}", + "start": {"service": "script.vacuum_start"}, + } + }, + } + }, + ) await hass.async_start() await hass.async_block_till_done()