Have template platforms never leave the event loop

This commit is contained in:
Paulus Schoutsen 2016-09-30 21:38:39 -07:00
parent 3e24a35c1e
commit 4198c42736
6 changed files with 56 additions and 17 deletions

View File

@ -4,6 +4,7 @@ Support for exposing a templated binary sensor.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/ https://home-assistant.io/components/binary_sensor.template/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -81,9 +82,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self.update() self.update()
@asyncio.coroutine
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."""
self.update_ha_state(True) yield from self.async_update_ha_state(True)
track_state_change(hass, entity_ids, template_bsensor_state_listener) track_state_change(hass, entity_ids, template_bsensor_state_listener)
@ -107,10 +109,11 @@ class BinarySensorTemplate(BinarySensorDevice):
"""No polling needed.""" """No polling needed."""
return False return False
def update(self): @asyncio.coroutine
def async_update(self):
"""Get the latest data and update the state.""" """Get the latest data and update the state."""
try: try:
self._state = self._template.render().lower() == 'true' self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex: except TemplateError as ex:
if ex.args and ex.args[0].startswith( if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"): "UndefinedError: 'None' has no attribute"):

View File

@ -4,6 +4,7 @@ Allows the creation of a sensor that breaks out state_attributes.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.template/ https://home-assistant.io/components/sensor.template/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -78,9 +79,10 @@ class SensorTemplate(Entity):
self.update() self.update()
@asyncio.coroutine
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."""
self.update_ha_state(True) yield from self.async_update_ha_state(True)
track_state_change(hass, entity_ids, template_sensor_state_listener) track_state_change(hass, entity_ids, template_sensor_state_listener)
@ -104,10 +106,11 @@ class SensorTemplate(Entity):
"""No polling needed.""" """No polling needed."""
return False return False
def update(self): @asyncio.coroutine
def async_update(self):
"""Get the latest data and update the states.""" """Get the latest data and update the states."""
try: try:
self._state = self._template.render() self._state = self._template.async_render()
except TemplateError as ex: except TemplateError as ex:
if ex.args and ex.args[0].startswith( if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"): "UndefinedError: 'None' has no attribute"):

View File

@ -4,6 +4,7 @@ Support for switches which integrates with other components.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.template/ https://home-assistant.io/components/switch.template/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -87,9 +88,10 @@ class SwitchTemplate(SwitchDevice):
self.update() self.update()
@asyncio.coroutine
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."""
self.update_ha_state(True) yield from self.async_update_ha_state(True)
track_state_change(hass, entity_ids, template_switch_state_listener) track_state_change(hass, entity_ids, template_switch_state_listener)
@ -121,10 +123,11 @@ class SwitchTemplate(SwitchDevice):
"""Fire the off action.""" """Fire the off action."""
self._off_script.run() self._off_script.run()
def update(self): @asyncio.coroutine
def async_update(self):
"""Update the state from the template.""" """Update the state from the template."""
try: try:
state = self._template.render().lower() state = self._template.async_render().lower()
if state in _VALID_STATES: if state in _VALID_STATES:
self._state = state in ('true', STATE_ON) self._state = state in ('true', STATE_ON)

View File

@ -49,6 +49,11 @@ class Entity(object):
# SAFE TO OVERWRITE # SAFE TO OVERWRITE
# The properties and methods here are safe to overwrite when inheriting # The properties and methods here are safe to overwrite when inheriting
# this class. These may be used to customize the behavior of the entity. # this class. These may be used to customize the behavior of the entity.
entity_id = None # type: str
# Owning hass instance. Will be set by EntityComponent
hass = None # type: Optional[HomeAssistant]
@property @property
def should_poll(self) -> bool: def should_poll(self) -> bool:
"""Return True if entity has to be polled for state. """Return True if entity has to be polled for state.
@ -128,18 +133,22 @@ class Entity(object):
return False return False
def update(self): def update(self):
"""Retrieve latest state.""" """Retrieve latest state.
pass
entity_id = None # type: str When not implemented, will forward call to async version if available.
"""
async_update = getattr(self, 'async_update', None)
if async_update is None:
return
run_coroutine_threadsafe(async_update(), self.hass.loop).result()
# DO NOT OVERWRITE # DO NOT OVERWRITE
# These properties and methods are either managed by Home Assistant or they # These properties and methods are either managed by Home Assistant or they
# are used to perform a very specific function. Overwriting these may # are used to perform a very specific function. Overwriting these may
# produce undesirable effects in the entity's operation. # produce undesirable effects in the entity's operation.
hass = None # type: Optional[HomeAssistant]
def update_ha_state(self, force_refresh=False): def update_ha_state(self, force_refresh=False):
"""Update Home Assistant with current state of entity. """Update Home Assistant with current state of entity.
@ -172,7 +181,7 @@ class Entity(object):
if force_refresh: if force_refresh:
if hasattr(self, 'async_update'): if hasattr(self, 'async_update'):
# pylint: disable=no-member # pylint: disable=no-member
self.async_update() yield from self.async_update()
else: else:
# PS: Run this in our own thread pool once we have # PS: Run this in our own thread pool once we have
# future support? # future support?

View File

@ -119,7 +119,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
vs.update_ha_state() vs.update_ha_state()
self.hass.block_till_done() self.hass.block_till_done()
with mock.patch.object(vs, 'update') as mock_update: with mock.patch.object(vs, 'async_update') as mock_update:
self.hass.bus.fire(EVENT_STATE_CHANGED) self.hass.bus.fire(EVENT_STATE_CHANGED)
self.hass.block_till_done() self.hass.block_till_done()
assert mock_update.call_count == 1 assert mock_update.call_count == 1

View File

@ -53,7 +53,12 @@ def test_async_update_support(event_loop):
assert len(sync_update) == 1 assert len(sync_update) == 1
assert len(async_update) == 0 assert len(async_update) == 0
ent.async_update = lambda: async_update.append(1) @asyncio.coroutine
def async_update_func():
"""Async update."""
async_update.append(1)
ent.async_update = async_update_func
event_loop.run_until_complete(test()) event_loop.run_until_complete(test())
@ -95,3 +100,19 @@ class TestHelpersEntity(object):
assert entity.generate_entity_id( assert entity.generate_entity_id(
fmt, 'overwrite hidden true', fmt, 'overwrite hidden true',
hass=self.hass) == 'test.overwrite_hidden_true_2' hass=self.hass) == 'test.overwrite_hidden_true_2'
def test_update_calls_async_update_if_available(self):
"""Test async update getting called."""
async_update = []
class AsyncEntity(entity.Entity):
hass = self.hass
entity_id = 'sensor.test'
@asyncio.coroutine
def async_update(self):
async_update.append([1])
ent = AsyncEntity()
ent.update()
assert len(async_update) == 1