Async template (#3909)
* port binary_sensor/template * port sensor/template * port switch/template * fix unittest * fix * use task instead yield on it * fix unittest * fix unittest v2 * fix invalid config * fix lint * fix unuset import
This commit is contained in:
parent
555e533f67
commit
1540bb1279
4 changed files with 81 additions and 67 deletions
|
@ -17,8 +17,8 @@ from homeassistant.const import (
|
|||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_SENSORS)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
def async_setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup template binary sensors."""
|
||||
sensors = []
|
||||
|
||||
|
@ -61,8 +61,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
if not sensors:
|
||||
_LOGGER.error('No sensors added')
|
||||
return False
|
||||
add_devices(sensors)
|
||||
|
||||
hass.loop.create_task(add_devices(sensors))
|
||||
return True
|
||||
|
||||
|
||||
|
@ -74,21 +74,22 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||
value_template, entity_ids):
|
||||
"""Initialize the Template binary sensor."""
|
||||
self.hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
|
||||
hass=hass)
|
||||
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
|
||||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._sensor_class = sensor_class
|
||||
self._template = value_template
|
||||
self._state = None
|
||||
|
||||
self.update()
|
||||
self._async_render()
|
||||
|
||||
@callback
|
||||
def template_bsensor_state_listener(entity, old_state, new_state):
|
||||
"""Called when the target device changes state."""
|
||||
hass.loop.create_task(self.async_update_ha_state(True))
|
||||
|
||||
track_state_change(hass, entity_ids, template_bsensor_state_listener)
|
||||
async_track_state_change(
|
||||
hass, entity_ids, template_bsensor_state_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -112,7 +113,11 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
"""Get the latest data and update the state."""
|
||||
"""Update the state from the template."""
|
||||
self._async_render()
|
||||
|
||||
def _async_render(self):
|
||||
"""Render the state from the template."""
|
||||
try:
|
||||
self._state = self._template.async_render().lower() == 'true'
|
||||
except TemplateError as ex:
|
||||
|
|
|
@ -15,8 +15,8 @@ from homeassistant.const import (
|
|||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
|
||||
ATTR_ENTITY_ID, CONF_SENSORS)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers.entity import Entity, generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.entity import Entity, async_generate_entity_id
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
def async_setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the template sensors."""
|
||||
sensors = []
|
||||
|
||||
|
@ -59,7 +59,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
if not sensors:
|
||||
_LOGGER.error("No sensors added")
|
||||
return False
|
||||
add_devices(sensors)
|
||||
|
||||
hass.loop.create_task(add_devices(sensors))
|
||||
return True
|
||||
|
||||
|
||||
|
@ -71,21 +72,23 @@ class SensorTemplate(Entity):
|
|||
state_template, entity_ids):
|
||||
"""Initialize the sensor."""
|
||||
self.hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._template = state_template
|
||||
self._state = None
|
||||
|
||||
self.update()
|
||||
# update state
|
||||
self._async_render()
|
||||
|
||||
@callback
|
||||
def template_sensor_state_listener(entity, old_state, new_state):
|
||||
"""Called when the target device changes state."""
|
||||
hass.loop.create_task(self.async_update_ha_state(True))
|
||||
|
||||
track_state_change(hass, entity_ids, template_sensor_state_listener)
|
||||
async_track_state_change(
|
||||
hass, entity_ids, template_sensor_state_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -109,7 +112,11 @@ class SensorTemplate(Entity):
|
|||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
"""Get the latest data and update the states."""
|
||||
"""Update the state from the template."""
|
||||
self._async_render()
|
||||
|
||||
def _async_render(self):
|
||||
"""Render the state from the template."""
|
||||
try:
|
||||
self._state = self._template.async_render()
|
||||
except TemplateError as ex:
|
||||
|
|
|
@ -16,8 +16,8 @@ from homeassistant.const import (
|
|||
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON,
|
||||
ATTR_ENTITY_ID, CONF_SWITCHES)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.helpers.script import Script
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
def async_setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Template switch."""
|
||||
switches = []
|
||||
|
||||
|
@ -53,6 +53,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||
state_template.extract_entities())
|
||||
|
||||
state_template.hass = hass
|
||||
|
||||
switches.append(
|
||||
SwitchTemplate(
|
||||
hass,
|
||||
|
@ -66,7 +68,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
if not switches:
|
||||
_LOGGER.error("No switches added")
|
||||
return False
|
||||
add_devices(switches)
|
||||
|
||||
hass.loop.create_task(add_devices(switches))
|
||||
return True
|
||||
|
||||
|
||||
|
@ -78,23 +81,23 @@ class SwitchTemplate(SwitchDevice):
|
|||
on_action, off_action, entity_ids):
|
||||
"""Initialize the Template switch."""
|
||||
self.hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._template = state_template
|
||||
state_template.hass = hass
|
||||
self._on_script = Script(hass, on_action)
|
||||
self._off_script = Script(hass, off_action)
|
||||
self._state = False
|
||||
|
||||
self.update()
|
||||
self._async_render()
|
||||
|
||||
@callback
|
||||
def template_switch_state_listener(entity, old_state, new_state):
|
||||
"""Called when the target device changes state."""
|
||||
hass.loop.create_task(self.async_update_ha_state(True))
|
||||
|
||||
track_state_change(hass, entity_ids, template_switch_state_listener)
|
||||
async_track_state_change(
|
||||
hass, entity_ids, template_switch_state_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -127,6 +130,10 @@ class SwitchTemplate(SwitchDevice):
|
|||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
"""Update the state from the template."""
|
||||
self._async_render()
|
||||
|
||||
def _async_render(self):
|
||||
"""Render the state from the template."""
|
||||
try:
|
||||
state = self._template.async_render().lower()
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ from unittest import mock
|
|||
|
||||
from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.components.binary_sensor import template
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template as template_hlpr
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
from tests.common import get_test_home_assistant, assert_setup_component
|
||||
|
||||
|
@ -29,38 +29,27 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
@mock.patch.object(template, 'BinarySensorTemplate')
|
||||
def test_setup(self, mock_template):
|
||||
""""Test the setup."""
|
||||
tpl = template_hlpr.Template('{{ foo }}', self.hass)
|
||||
config = PLATFORM_SCHEMA({
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
'test': {
|
||||
'friendly_name': 'virtual thingy',
|
||||
'value_template': tpl,
|
||||
'sensor_class': 'motion',
|
||||
'entity_id': 'test'
|
||||
config = {
|
||||
'binary_sensor': {
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
'test': {
|
||||
'friendly_name': 'virtual thingy',
|
||||
'value_template': '{{ foo }}',
|
||||
'sensor_class': 'motion',
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
add_devices = mock.MagicMock()
|
||||
result = template.setup_platform(self.hass, config, add_devices)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual(mock_template.call_count, 1)
|
||||
self.assertEqual(
|
||||
mock_template.call_args,
|
||||
mock.call(
|
||||
self.hass, 'test', 'virtual thingy', 'motion', tpl, 'test'
|
||||
)
|
||||
)
|
||||
self.assertEqual(add_devices.call_count, 1)
|
||||
self.assertEqual(
|
||||
add_devices.call_args, mock.call([mock_template.return_value])
|
||||
)
|
||||
},
|
||||
}
|
||||
with assert_setup_component(1):
|
||||
assert bootstrap.setup_component(
|
||||
self.hass, 'binary_sensor', config)
|
||||
|
||||
def test_setup_no_sensors(self):
|
||||
""""Test setup with no sensors."""
|
||||
with assert_setup_component(0):
|
||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||
'binary_sensor': {
|
||||
'platform': 'template'
|
||||
}
|
||||
})
|
||||
|
@ -68,8 +57,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
def test_setup_invalid_device(self):
|
||||
""""Test the setup with invalid devices."""
|
||||
with assert_setup_component(0):
|
||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||
'binary_sensor': {
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
'foo bar': {},
|
||||
|
@ -80,8 +69,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
def test_setup_invalid_sensor_class(self):
|
||||
""""Test setup with invalid sensor class."""
|
||||
with assert_setup_component(0):
|
||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||
'binary_sensor': {
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
'test': {
|
||||
|
@ -95,8 +84,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
def test_setup_invalid_missing_template(self):
|
||||
""""Test setup with invalid and missing template."""
|
||||
with assert_setup_component(0):
|
||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
||||
'sensor': {
|
||||
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||
'binary_sensor': {
|
||||
'platform': 'template',
|
||||
'sensors': {
|
||||
'test': {
|
||||
|
@ -108,9 +97,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
|
||||
def test_attributes(self):
|
||||
""""Test the attributes."""
|
||||
vs = template.BinarySensorTemplate(
|
||||
vs = run_callback_threadsafe(
|
||||
self.hass.loop, template.BinarySensorTemplate,
|
||||
self.hass, 'parent', 'Parent', 'motion',
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL
|
||||
).result()
|
||||
self.assertFalse(vs.should_poll)
|
||||
self.assertEqual('motion', vs.sensor_class)
|
||||
self.assertEqual('Parent', vs.name)
|
||||
|
@ -126,9 +117,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
|
||||
def test_event(self):
|
||||
""""Test the event."""
|
||||
vs = template.BinarySensorTemplate(
|
||||
vs = run_callback_threadsafe(
|
||||
self.hass.loop, template.BinarySensorTemplate,
|
||||
self.hass, 'parent', 'Parent', 'motion',
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL
|
||||
).result()
|
||||
vs.update_ha_state()
|
||||
self.hass.block_till_done()
|
||||
|
||||
|
@ -140,9 +133,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||
@mock.patch('homeassistant.helpers.template.Template.render')
|
||||
def test_update_template_error(self, mock_render):
|
||||
""""Test the template update error."""
|
||||
vs = template.BinarySensorTemplate(
|
||||
vs = run_callback_threadsafe(
|
||||
self.hass.loop, template.BinarySensorTemplate,
|
||||
self.hass, 'parent', 'Parent', 'motion',
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL)
|
||||
template_hlpr.Template('{{ 1 > 1 }}', self.hass), MATCH_ALL
|
||||
).result()
|
||||
mock_render.side_effect = TemplateError('foo')
|
||||
vs.update()
|
||||
mock_render.side_effect = TemplateError(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue