Add availability_template to Template Fan platform (#26511)

* Added availability_template to Template Fan platform

* Added to test for invalid values in availability_template

* fixed component ID in test

* Made availability_template redering erorr more concise

* Updated AVAILABILITY_TEMPLATE Rendering error

* Moved const to package Const.py

* Fix import order (pylint)

* Removed 'Magic' string

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

* reverted _available back to boolean

* Fixed tests (magic values and state checks)
pull/27034/head
Gil Peeters 2019-09-28 21:59:40 +10:00 committed by Charles Garwood
parent 5c5f6a21af
commit 74196eaf8b
2 changed files with 108 additions and 1 deletions

View File

@ -33,6 +33,7 @@ from homeassistant.core import callback
from homeassistant.exceptions import TemplateError
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__)
@ -58,6 +59,7 @@ FAN_SCHEMA = vol.Schema(
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 @@ class TemplateFan(FanEntity):
speed_template,
oscillating_template,
direction_template,
availability_template,
on_action,
off_action,
set_speed_action,
@ -175,6 +181,8 @@ class TemplateFan(FanEntity):
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 @@ class TemplateFan(FanEntity):
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 @@ class TemplateFan(FanEntity):
"""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,17 @@ class TemplateFan(FanEntity):
", ".join(_VALID_DIRECTIONS),
)
self._direction = 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,
)

View File

@ -5,7 +5,7 @@ import pytest
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 @@ _LOGGER = logging.getLogger(__name__)
_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,49 @@ 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."""
with assert_setup_component(1, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"availability_template": "{{ is_state('availability_boolean.state', 'on') }}",
"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, STATE_ON)
await hass.async_block_till_done()
# Device State should not be unavailable
assert hass.states.get(_TEST_FAN).state != STATE_UNAVAILABLE
# When Availability template returns false
hass.states.async_set(_STATE_AVAILABILITY_BOOLEAN, STATE_OFF)
await hass.async_block_till_done()
# device state should be unavailable
assert hass.states.get(_TEST_FAN).state == STATE_UNAVAILABLE
async def test_templates_with_valid_values(hass, calls):
"""Test templates with valid values."""
with assert_setup_component(1, "fan"):
@ -272,6 +317,39 @@ async def test_templates_invalid_values(hass, calls):
_verify(hass, STATE_OFF, None, None, None)
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, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'on' }}",
"availability_template": "{{ x - 12 }}",
"speed_template": "{{ states('input_select.speed') }}",
"oscillating_template": "{{ states('input_select.osc') }}",
"direction_template": "{{ states('input_select.direction') }}",
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
},
}
},
)
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.get("fan.test_fan").state != STATE_UNAVAILABLE
assert ("Could not render availability_template template") in caplog.text
assert ("UndefinedError: 'x' is undefined") in caplog.text
# End of template tests #