Add availability_template to Template Cover platform (#26509)

* Added availability_template to Template Cover platform

* Added to test for invalid values in availability_template

* Updated AVAILABILITY_TEMPLATE Rendering error

* Moved const to package Const.py

* Fix import order (pylint)

* Moved availability_template rendering to common loop

* Removed 'Magic' string and removed duplicate code

* Cleaned up const and compare lowercase result to 'true'

* reverted _available back to boolean

* Fixed tests (async, magic values and state checks)
This commit is contained in:
Gil Peeters 2019-09-28 21:53:16 +10:00 committed by Charles Garwood
parent f3d408aca4
commit 6d773198a1
2 changed files with 137 additions and 11 deletions

View File

@ -38,6 +38,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.script import Script
from .const import CONF_AVAILABILITY_TEMPLATE
_LOGGER = logging.getLogger(__name__)
_VALID_STATES = [STATE_OPEN, STATE_CLOSED, "true", "false"]
@ -74,6 +75,7 @@ COVER_SCHEMA = vol.Schema(
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 @@ class CoverTemplate(CoverDevice):
tilt_template,
icon_template,
entity_picture_template,
availability_template,
open_action,
close_action,
stop_action,
@ -213,6 +223,7 @@ class CoverTemplate(CoverDevice):
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 @@ class CoverTemplate(CoverDevice):
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 @@ class CoverTemplate(CoverDevice):
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 @@ class CoverTemplate(CoverDevice):
"""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:
@ -430,11 +449,8 @@ class CoverTemplate(CoverDevice):
)
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:
@ -447,22 +463,23 @@ class CoverTemplate(CoverDevice):
)
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
for property_name, template in (
("_icon", self._icon_template),
("_entity_picture", self._entity_picture_template),
("_available", self._availability_template),
):
if template is None:
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(

View File

@ -16,7 +16,10 @@ from homeassistant.const import (
SERVICE_SET_COVER_TILT_POSITION,
SERVICE_STOP_COVER,
STATE_CLOSED,
STATE_UNAVAILABLE,
STATE_OPEN,
STATE_ON,
STATE_OFF,
)
from tests.common import assert_setup_component, async_mock_service
@ -839,6 +842,112 @@ 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": "{{ is_state('availability_state.state','on') }}",
}
},
}
},
)
await hass.async_start()
await hass.async_block_till_done()
hass.states.async_set("availability_state.state", STATE_OFF)
await hass.async_block_till_done()
assert hass.states.get("cover.test_template_cover").state == STATE_UNAVAILABLE
hass.states.async_set("availability_state.state", STATE_ON)
await hass.async_block_till_done()
assert hass.states.get("cover.test_template_cover").state != STATE_UNAVAILABLE
async def test_availability_without_availability_template(hass, calls):
"""Test that component is availble if there is no."""
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_invalid_availability_template_keeps_component_available(hass, caplog):
"""Test that an invalid availability keeps the device available."""
assert await setup.async_setup_component(
hass,
"cover",
{
"cover": {
"platform": "template",
"covers": {
"test_template_cover": {
"availability_template": "{{ x - 12 }}",
"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()
assert hass.states.get("cover.test_template_cover") != STATE_UNAVAILABLE
assert ("UndefinedError: 'x' is undefined") in caplog.text
async def test_device_class(hass, calls):
"""Test device class."""
with assert_setup_component(1, "cover"):