mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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
@ -17,8 +17,8 @@ from homeassistant.const import (
|
|||||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
|
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
|
||||||
CONF_SENSOR_CLASS, CONF_SENSORS)
|
CONF_SENSOR_CLASS, CONF_SENSORS)
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers.entity import generate_entity_id
|
from homeassistant.helpers.entity import async_generate_entity_id
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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."""
|
"""Setup template binary sensors."""
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
@ -61,8 +61,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if not sensors:
|
if not sensors:
|
||||||
_LOGGER.error('No sensors added')
|
_LOGGER.error('No sensors added')
|
||||||
return False
|
return False
|
||||||
add_devices(sensors)
|
|
||||||
|
|
||||||
|
hass.loop.create_task(add_devices(sensors))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -74,21 +74,22 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||||||
value_template, entity_ids):
|
value_template, entity_ids):
|
||||||
"""Initialize the Template binary sensor."""
|
"""Initialize the Template binary sensor."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
|
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
|
||||||
hass=hass)
|
hass=hass)
|
||||||
self._name = friendly_name
|
self._name = friendly_name
|
||||||
self._sensor_class = sensor_class
|
self._sensor_class = sensor_class
|
||||||
self._template = value_template
|
self._template = value_template
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
self.update()
|
self._async_render()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def template_bsensor_state_listener(entity, old_state, new_state):
|
def template_bsensor_state_listener(entity, old_state, new_state):
|
||||||
"""Called when the target device changes state."""
|
"""Called when the target device changes state."""
|
||||||
hass.loop.create_task(self.async_update_ha_state(True))
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -112,7 +113,11 @@ class BinarySensorTemplate(BinarySensorDevice):
|
|||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
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:
|
try:
|
||||||
self._state = self._template.async_render().lower() == 'true'
|
self._state = self._template.async_render().lower() == 'true'
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
|
@ -15,8 +15,8 @@ from homeassistant.const import (
|
|||||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
|
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
|
||||||
ATTR_ENTITY_ID, CONF_SENSORS)
|
ATTR_ENTITY_ID, CONF_SENSORS)
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers.entity import Entity, generate_entity_id
|
from homeassistant.helpers.entity import Entity, async_generate_entity_id
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# 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."""
|
"""Setup the template sensors."""
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
@ -59,7 +59,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if not sensors:
|
if not sensors:
|
||||||
_LOGGER.error("No sensors added")
|
_LOGGER.error("No sensors added")
|
||||||
return False
|
return False
|
||||||
add_devices(sensors)
|
|
||||||
|
hass.loop.create_task(add_devices(sensors))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -71,21 +72,23 @@ class SensorTemplate(Entity):
|
|||||||
state_template, entity_ids):
|
state_template, entity_ids):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||||
hass=hass)
|
hass=hass)
|
||||||
self._name = friendly_name
|
self._name = friendly_name
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self._unit_of_measurement = unit_of_measurement
|
||||||
self._template = state_template
|
self._template = state_template
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
self.update()
|
# update state
|
||||||
|
self._async_render()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def template_sensor_state_listener(entity, old_state, new_state):
|
def template_sensor_state_listener(entity, old_state, new_state):
|
||||||
"""Called when the target device changes state."""
|
"""Called when the target device changes state."""
|
||||||
hass.loop.create_task(self.async_update_ha_state(True))
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -109,7 +112,11 @@ class SensorTemplate(Entity):
|
|||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
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:
|
try:
|
||||||
self._state = self._template.async_render()
|
self._state = self._template.async_render()
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
|
@ -16,8 +16,8 @@ from homeassistant.const import (
|
|||||||
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON,
|
ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON,
|
||||||
ATTR_ENTITY_ID, CONF_SWITCHES)
|
ATTR_ENTITY_ID, CONF_SWITCHES)
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers.entity import generate_entity_id
|
from homeassistant.helpers.entity import async_generate_entity_id
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
from homeassistant.helpers.script import Script
|
from homeassistant.helpers.script import Script
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# 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."""
|
"""Setup the Template switch."""
|
||||||
switches = []
|
switches = []
|
||||||
|
|
||||||
@ -53,6 +53,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||||
state_template.extract_entities())
|
state_template.extract_entities())
|
||||||
|
|
||||||
|
state_template.hass = hass
|
||||||
|
|
||||||
switches.append(
|
switches.append(
|
||||||
SwitchTemplate(
|
SwitchTemplate(
|
||||||
hass,
|
hass,
|
||||||
@ -66,7 +68,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if not switches:
|
if not switches:
|
||||||
_LOGGER.error("No switches added")
|
_LOGGER.error("No switches added")
|
||||||
return False
|
return False
|
||||||
add_devices(switches)
|
|
||||||
|
hass.loop.create_task(add_devices(switches))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -78,23 +81,23 @@ class SwitchTemplate(SwitchDevice):
|
|||||||
on_action, off_action, entity_ids):
|
on_action, off_action, entity_ids):
|
||||||
"""Initialize the Template switch."""
|
"""Initialize the Template switch."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||||
hass=hass)
|
hass=hass)
|
||||||
self._name = friendly_name
|
self._name = friendly_name
|
||||||
self._template = state_template
|
self._template = state_template
|
||||||
state_template.hass = hass
|
|
||||||
self._on_script = Script(hass, on_action)
|
self._on_script = Script(hass, on_action)
|
||||||
self._off_script = Script(hass, off_action)
|
self._off_script = Script(hass, off_action)
|
||||||
self._state = False
|
self._state = False
|
||||||
|
|
||||||
self.update()
|
self._async_render()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def template_switch_state_listener(entity, old_state, new_state):
|
def template_switch_state_listener(entity, old_state, new_state):
|
||||||
"""Called when the target device changes state."""
|
"""Called when the target device changes state."""
|
||||||
hass.loop.create_task(self.async_update_ha_state(True))
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -127,6 +130,10 @@ class SwitchTemplate(SwitchDevice):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
def async_update(self):
|
||||||
"""Update the state from the template."""
|
"""Update the state from the template."""
|
||||||
|
self._async_render()
|
||||||
|
|
||||||
|
def _async_render(self):
|
||||||
|
"""Render the state from the template."""
|
||||||
try:
|
try:
|
||||||
state = self._template.async_render().lower()
|
state = self._template.async_render().lower()
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ from unittest import mock
|
|||||||
|
|
||||||
from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
|
from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
|
||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
|
|
||||||
from homeassistant.components.binary_sensor import template
|
from homeassistant.components.binary_sensor import template
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import template as template_hlpr
|
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
|
from tests.common import get_test_home_assistant, assert_setup_component
|
||||||
|
|
||||||
@ -29,38 +29,27 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
@mock.patch.object(template, 'BinarySensorTemplate')
|
@mock.patch.object(template, 'BinarySensorTemplate')
|
||||||
def test_setup(self, mock_template):
|
def test_setup(self, mock_template):
|
||||||
""""Test the setup."""
|
""""Test the setup."""
|
||||||
tpl = template_hlpr.Template('{{ foo }}', self.hass)
|
config = {
|
||||||
config = PLATFORM_SCHEMA({
|
'binary_sensor': {
|
||||||
'platform': 'template',
|
'platform': 'template',
|
||||||
'sensors': {
|
'sensors': {
|
||||||
'test': {
|
'test': {
|
||||||
'friendly_name': 'virtual thingy',
|
'friendly_name': 'virtual thingy',
|
||||||
'value_template': tpl,
|
'value_template': '{{ foo }}',
|
||||||
'sensor_class': 'motion',
|
'sensor_class': 'motion',
|
||||||
'entity_id': 'test'
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
})
|
}
|
||||||
add_devices = mock.MagicMock()
|
with assert_setup_component(1):
|
||||||
result = template.setup_platform(self.hass, config, add_devices)
|
assert bootstrap.setup_component(
|
||||||
self.assertTrue(result)
|
self.hass, 'binary_sensor', config)
|
||||||
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])
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_setup_no_sensors(self):
|
def test_setup_no_sensors(self):
|
||||||
""""Test setup with no sensors."""
|
""""Test setup with no sensors."""
|
||||||
with assert_setup_component(0):
|
with assert_setup_component(0):
|
||||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||||
'sensor': {
|
'binary_sensor': {
|
||||||
'platform': 'template'
|
'platform': 'template'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -68,8 +57,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
def test_setup_invalid_device(self):
|
def test_setup_invalid_device(self):
|
||||||
""""Test the setup with invalid devices."""
|
""""Test the setup with invalid devices."""
|
||||||
with assert_setup_component(0):
|
with assert_setup_component(0):
|
||||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||||
'sensor': {
|
'binary_sensor': {
|
||||||
'platform': 'template',
|
'platform': 'template',
|
||||||
'sensors': {
|
'sensors': {
|
||||||
'foo bar': {},
|
'foo bar': {},
|
||||||
@ -80,8 +69,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
def test_setup_invalid_sensor_class(self):
|
def test_setup_invalid_sensor_class(self):
|
||||||
""""Test setup with invalid sensor class."""
|
""""Test setup with invalid sensor class."""
|
||||||
with assert_setup_component(0):
|
with assert_setup_component(0):
|
||||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||||
'sensor': {
|
'binary_sensor': {
|
||||||
'platform': 'template',
|
'platform': 'template',
|
||||||
'sensors': {
|
'sensors': {
|
||||||
'test': {
|
'test': {
|
||||||
@ -95,8 +84,8 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
def test_setup_invalid_missing_template(self):
|
def test_setup_invalid_missing_template(self):
|
||||||
""""Test setup with invalid and missing template."""
|
""""Test setup with invalid and missing template."""
|
||||||
with assert_setup_component(0):
|
with assert_setup_component(0):
|
||||||
assert bootstrap.setup_component(self.hass, 'sensor', {
|
assert bootstrap.setup_component(self.hass, 'binary_sensor', {
|
||||||
'sensor': {
|
'binary_sensor': {
|
||||||
'platform': 'template',
|
'platform': 'template',
|
||||||
'sensors': {
|
'sensors': {
|
||||||
'test': {
|
'test': {
|
||||||
@ -108,9 +97,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
|
|
||||||
def test_attributes(self):
|
def test_attributes(self):
|
||||||
""""Test the attributes."""
|
""""Test the attributes."""
|
||||||
vs = template.BinarySensorTemplate(
|
vs = run_callback_threadsafe(
|
||||||
|
self.hass.loop, template.BinarySensorTemplate,
|
||||||
self.hass, 'parent', 'Parent', 'motion',
|
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.assertFalse(vs.should_poll)
|
||||||
self.assertEqual('motion', vs.sensor_class)
|
self.assertEqual('motion', vs.sensor_class)
|
||||||
self.assertEqual('Parent', vs.name)
|
self.assertEqual('Parent', vs.name)
|
||||||
@ -126,9 +117,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
|
|
||||||
def test_event(self):
|
def test_event(self):
|
||||||
""""Test the event."""
|
""""Test the event."""
|
||||||
vs = template.BinarySensorTemplate(
|
vs = run_callback_threadsafe(
|
||||||
|
self.hass.loop, template.BinarySensorTemplate,
|
||||||
self.hass, 'parent', 'Parent', 'motion',
|
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()
|
vs.update_ha_state()
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
|
|
||||||
@ -140,9 +133,11 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
|||||||
@mock.patch('homeassistant.helpers.template.Template.render')
|
@mock.patch('homeassistant.helpers.template.Template.render')
|
||||||
def test_update_template_error(self, mock_render):
|
def test_update_template_error(self, mock_render):
|
||||||
""""Test the template update error."""
|
""""Test the template update error."""
|
||||||
vs = template.BinarySensorTemplate(
|
vs = run_callback_threadsafe(
|
||||||
|
self.hass.loop, template.BinarySensorTemplate,
|
||||||
self.hass, 'parent', 'Parent', 'motion',
|
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')
|
mock_render.side_effect = TemplateError('foo')
|
||||||
vs.update()
|
vs.update()
|
||||||
mock_render.side_effect = TemplateError(
|
mock_render.side_effect = TemplateError(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user