diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index d179edfc1d8..d5791edc81a 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -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: diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 1abd1d2fd94..6cd7d61b641 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -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: diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index b6ce400d0ac..b8820de972f 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -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() diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index 28098b2f2a0..98462083e6f 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -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(