Skip to content
Merged
3 changes: 3 additions & 0 deletions homeassistant/components/template/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the Template Platform Components."""

CONF_AVAILABILITY_TEMPLATE = "availability_template"
27 changes: 27 additions & 0 deletions homeassistant/components/template/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,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"
Expand All @@ -67,6 +69,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,
Expand Down Expand Up @@ -94,6 +97,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)
Expand All @@ -113,6 +117,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
Expand Down Expand Up @@ -152,6 +157,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,
Expand All @@ -178,6 +184,7 @@ def __init__(
state_template,
battery_level_template,
fan_speed_template,
availability_template,
start_action,
pause_action,
stop_action,
Expand All @@ -198,6 +205,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)
Expand Down Expand Up @@ -235,6 +243,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
Expand Down Expand Up @@ -280,6 +289,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)
Expand Down Expand Up @@ -421,3 +435,16 @@ 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:
self._available = (
self._availability_template.async_render().lower() == "true"
)
except (TemplateError, ValueError) as ex:
_LOGGER.error(
"Could not render %s template %s: %s",
CONF_AVAILABILITY_TEMPLATE,
self._name,
ex,
)
64 changes: 63 additions & 1 deletion tests/components/template/test_vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_OFF, STATE_UNKNOWN, STATE_UNAVAILABLE
from homeassistant.components.vacuum import (
ATTR_BATTERY_LEVEL,
STATE_CLEANING,
Expand Down Expand Up @@ -210,6 +210,68 @@ 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."""

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_state.state", STATE_ON)
await hass.async_block_till_done()

# Device State should not be unavailable
assert hass.states.get("vacuum.test_template_vacuum").state != STATE_UNAVAILABLE

# When Availability template returns false
hass.states.async_set("availability_state.state", STATE_OFF)
await hass.async_block_till_done()

# device state should be 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."""
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 #


Expand Down