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
https://home-assistant.io/components/binary_sensor.template/
"""
import asyncio
import logging
import voluptuous as vol
@ -81,9 +82,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self.update()
@asyncio.coroutine
def template_bsensor_state_listener(entity, old_state, new_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)
@ -107,10 +109,11 @@ class BinarySensorTemplate(BinarySensorDevice):
"""No polling needed."""
return False
def update(self):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the state."""
try:
self._state = self._template.render().lower() == 'true'
self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"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
https://home-assistant.io/components/sensor.template/
"""
import asyncio
import logging
import voluptuous as vol
@ -78,9 +79,10 @@ class SensorTemplate(Entity):
self.update()
@asyncio.coroutine
def template_sensor_state_listener(entity, old_state, new_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)
@ -104,10 +106,11 @@ class SensorTemplate(Entity):
"""No polling needed."""
return False
def update(self):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the states."""
try:
self._state = self._template.render()
self._state = self._template.async_render()
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"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
https://home-assistant.io/components/switch.template/
"""
import asyncio
import logging
import voluptuous as vol
@ -87,9 +88,10 @@ class SwitchTemplate(SwitchDevice):
self.update()
@asyncio.coroutine
def template_switch_state_listener(entity, old_state, new_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)
@ -121,10 +123,11 @@ class SwitchTemplate(SwitchDevice):
"""Fire the off action."""
self._off_script.run()
def update(self):
@asyncio.coroutine
def async_update(self):
"""Update the state from the template."""
try:
state = self._template.render().lower()
state = self._template.async_render().lower()
if state in _VALID_STATES:
self._state = state in ('true', STATE_ON)

View file

@ -49,6 +49,11 @@ class Entity(object):
# SAFE TO OVERWRITE
# The properties and methods here are safe to overwrite when inheriting
# 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
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
@ -128,18 +133,22 @@ class Entity(object):
return False
def update(self):
"""Retrieve latest state."""
pass
"""Retrieve latest state.
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
# These properties and methods are either managed by Home Assistant or they
# are used to perform a very specific function. Overwriting these may
# produce undesirable effects in the entity's operation.
hass = None # type: Optional[HomeAssistant]
def update_ha_state(self, force_refresh=False):
"""Update Home Assistant with current state of entity.
@ -172,7 +181,7 @@ class Entity(object):
if force_refresh:
if hasattr(self, 'async_update'):
# pylint: disable=no-member
self.async_update()
yield from self.async_update()
else:
# PS: Run this in our own thread pool once we have
# future support?

View file

@ -119,7 +119,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
vs.update_ha_state()
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.block_till_done()
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(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())
@ -95,3 +100,19 @@ class TestHelpersEntity(object):
assert entity.generate_entity_id(
fmt, 'overwrite hidden true',
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