Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
40 changes: 37 additions & 3 deletions homeassistant/components/template/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from homeassistant.exceptions import TemplateError
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__)

Expand All @@ -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,
}
)
Expand All @@ -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),
Expand All @@ -79,6 +92,7 @@ def __init__(
hass,
name,
value_template,
availability_template,
entity_ids,
command_lock,
command_unlock,
Expand All @@ -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."""
Expand Down Expand Up @@ -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:
Expand All @@ -148,6 +169,19 @@ 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:
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,
)

async def async_lock(self, **kwargs):
"""Lock the device."""
if self._optimistic:
Expand Down
70 changes: 67 additions & 3 deletions tests/components/template/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -332,3 +332,67 @@ def test_unlock_action(self):
self.hass.block_till_done()

assert len(self.calls) == 1


async def test_available_template_with_entities(hass):
"""Test availability templates with values from other entities."""

await setup.async_setup_component(
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": "{{ is_state('availability_state.state', 'on') }}",
}
},
)

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("lock.template_lock").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("lock.template_lock").state == STATE_UNAVAILABLE


async def test_invalid_availability_template_keeps_component_available(hass, caplog):
"""Test that an invalid availability keeps the device available."""
await setup.async_setup_component(
hass,
"lock",
{
"lock": {
"platform": "template",
"value_template": "{{ 1 + 1 }}",
"availability_template": "{{ x - 12 }}",
"lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"},
"unlock": {
"service": "switch.turn_off",
"entity_id": "switch.test_state",
},
}
},
)

await hass.async_start()
await hass.async_block_till_done()

assert hass.states.get("lock.template_lock").state != STATE_UNAVAILABLE
assert ("UndefinedError: 'x' is undefined") in caplog.text