Fix async probs (#9924)

* Update entity.py

* Update entity_component.py

* Update entity_component.py

* Update __init__.py

* Update entity_component.py

* Update entity_component.py

* Update entity.py

* cleanup entity

* Update entity_component.py

* Update entity_component.py

* Fix names & comments / fix tests

* Revert deadlock protection

* Add tests for entity

* Add test fix name

* Update other code

* Fix lint

* Remove restore state from template entities

* Lint
This commit is contained in:
Pascal Vizeli 2017-10-19 10:56:25 +02:00 committed by GitHub
parent 6cce934f72
commit c1b197419d
24 changed files with 356 additions and 362 deletions

View file

@ -124,20 +124,13 @@ def async_setup(hass, config):
method = "async_{}".format(SERVICE_TO_METHOD[service.service]) method = "async_{}".format(SERVICE_TO_METHOD[service.service])
update_tasks = []
for alarm in target_alarms: for alarm in target_alarms:
yield from getattr(alarm, method)(code) yield from getattr(alarm, method)(code)
update_tasks = []
for alarm in target_alarms:
if not alarm.should_poll: if not alarm.should_poll:
continue continue
update_tasks.append(alarm.async_update_ha_state(True))
update_coro = hass.async_add_job(
alarm.async_update_ha_state(True))
if hasattr(alarm, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -15,13 +15,12 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA) DEVICE_CLASSES_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE, ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, STATE_ON) CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change, async_track_same_state) async_track_state_change, async_track_same_state)
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -94,10 +93,6 @@ class BinarySensorTemplate(BinarySensorDevice):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
@callback @callback
def template_bsensor_state_listener(entity, old_state, new_state): def template_bsensor_state_listener(entity, old_state, new_state):
"""Handle the target device state changes.""" """Handle the target device state changes."""

View file

@ -126,23 +126,16 @@ def async_setup(hass, config):
"""Handle calls to the camera services.""" """Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service) target_cameras = component.async_extract_from_service(service)
update_tasks = []
for camera in target_cameras: for camera in target_cameras:
if service.service == SERVICE_EN_MOTION: if service.service == SERVICE_EN_MOTION:
yield from camera.async_enable_motion_detection() yield from camera.async_enable_motion_detection()
elif service.service == SERVICE_DISEN_MOTION: elif service.service == SERVICE_DISEN_MOTION:
yield from camera.async_disable_motion_detection() yield from camera.async_disable_motion_detection()
update_tasks = []
for camera in target_cameras:
if not camera.should_poll: if not camera.should_poll:
continue continue
update_tasks.append(camera.async_update_ha_state(True))
update_coro = hass.async_add_job(
camera.async_update_ha_state(True))
if hasattr(camera, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -236,24 +236,6 @@ def async_setup(hass, config):
load_yaml_config_file, load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml')) os.path.join(os.path.dirname(__file__), 'services.yaml'))
@asyncio.coroutine
def _async_update_climate(target_climate):
"""Update climate entity after service stuff."""
update_tasks = []
for climate in target_climate:
if not climate.should_poll:
continue
update_coro = hass.async_add_job(
climate.async_update_ha_state(True))
if hasattr(climate, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine @asyncio.coroutine
def async_away_mode_set_service(service): def async_away_mode_set_service(service):
"""Set away mode on target climate devices.""" """Set away mode on target climate devices."""
@ -261,13 +243,19 @@ def async_setup(hass, config):
away_mode = service.data.get(ATTR_AWAY_MODE) away_mode = service.data.get(ATTR_AWAY_MODE)
update_tasks = []
for climate in target_climate: for climate in target_climate:
if away_mode: if away_mode:
yield from climate.async_turn_away_mode_on() yield from climate.async_turn_away_mode_on()
else: else:
yield from climate.async_turn_away_mode_off() yield from climate.async_turn_away_mode_off()
yield from _async_update_climate(target_climate) if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_AWAY_MODE, async_away_mode_set_service, DOMAIN, SERVICE_SET_AWAY_MODE, async_away_mode_set_service,
@ -281,10 +269,16 @@ def async_setup(hass, config):
hold_mode = service.data.get(ATTR_HOLD_MODE) hold_mode = service.data.get(ATTR_HOLD_MODE)
update_tasks = []
for climate in target_climate: for climate in target_climate:
yield from climate.async_set_hold_mode(hold_mode) yield from climate.async_set_hold_mode(hold_mode)
yield from _async_update_climate(target_climate) if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_HOLD_MODE, async_hold_mode_set_service, DOMAIN, SERVICE_SET_HOLD_MODE, async_hold_mode_set_service,
@ -298,13 +292,19 @@ def async_setup(hass, config):
aux_heat = service.data.get(ATTR_AUX_HEAT) aux_heat = service.data.get(ATTR_AUX_HEAT)
update_tasks = []
for climate in target_climate: for climate in target_climate:
if aux_heat: if aux_heat:
yield from climate.async_turn_aux_heat_on() yield from climate.async_turn_aux_heat_on()
else: else:
yield from climate.async_turn_aux_heat_off() yield from climate.async_turn_aux_heat_off()
yield from _async_update_climate(target_climate) if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_AUX_HEAT, async_aux_heat_set_service, DOMAIN, SERVICE_SET_AUX_HEAT, async_aux_heat_set_service,
@ -316,6 +316,7 @@ def async_setup(hass, config):
"""Set temperature on the target climate devices.""" """Set temperature on the target climate devices."""
target_climate = component.async_extract_from_service(service) target_climate = component.async_extract_from_service(service)
update_tasks = []
for climate in target_climate: for climate in target_climate:
kwargs = {} kwargs = {}
for value, temp in service.data.items(): for value, temp in service.data.items():
@ -330,7 +331,12 @@ def async_setup(hass, config):
yield from climate.async_set_temperature(**kwargs) yield from climate.async_set_temperature(**kwargs)
yield from _async_update_climate(target_climate) if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_TEMPERATURE, async_temperature_set_service, DOMAIN, SERVICE_SET_TEMPERATURE, async_temperature_set_service,
@ -344,10 +350,15 @@ def async_setup(hass, config):
humidity = service.data.get(ATTR_HUMIDITY) humidity = service.data.get(ATTR_HUMIDITY)
update_tasks = []
for climate in target_climate: for climate in target_climate:
yield from climate.async_set_humidity(humidity) yield from climate.async_set_humidity(humidity)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate) if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_HUMIDITY, async_humidity_set_service, DOMAIN, SERVICE_SET_HUMIDITY, async_humidity_set_service,
@ -361,10 +372,15 @@ def async_setup(hass, config):
fan = service.data.get(ATTR_FAN_MODE) fan = service.data.get(ATTR_FAN_MODE)
update_tasks = []
for climate in target_climate: for climate in target_climate:
yield from climate.async_set_fan_mode(fan) yield from climate.async_set_fan_mode(fan)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate) if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_FAN_MODE, async_fan_mode_set_service, DOMAIN, SERVICE_SET_FAN_MODE, async_fan_mode_set_service,
@ -378,10 +394,15 @@ def async_setup(hass, config):
operation_mode = service.data.get(ATTR_OPERATION_MODE) operation_mode = service.data.get(ATTR_OPERATION_MODE)
update_tasks = []
for climate in target_climate: for climate in target_climate:
yield from climate.async_set_operation_mode(operation_mode) yield from climate.async_set_operation_mode(operation_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate) if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_OPERATION_MODE, async_operation_set_service, DOMAIN, SERVICE_SET_OPERATION_MODE, async_operation_set_service,
@ -395,10 +416,15 @@ def async_setup(hass, config):
swing_mode = service.data.get(ATTR_SWING_MODE) swing_mode = service.data.get(ATTR_SWING_MODE)
update_tasks = []
for climate in target_climate: for climate in target_climate:
yield from climate.async_set_swing_mode(swing_mode) yield from climate.async_set_swing_mode(swing_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate) if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SET_SWING_MODE, async_swing_set_service, DOMAIN, SERVICE_SET_SWING_MODE, async_swing_set_service,

View file

@ -169,21 +169,12 @@ def async_setup(hass, config):
params.pop(ATTR_ENTITY_ID, None) params.pop(ATTR_ENTITY_ID, None)
# call method # call method
update_tasks = []
for cover in covers: for cover in covers:
yield from getattr(cover, method['method'])(**params) yield from getattr(cover, method['method'])(**params)
update_tasks = []
for cover in covers:
if not cover.should_poll: if not cover.should_poll:
continue continue
update_tasks.append(cover.async_update_ha_state(True))
update_coro = hass.async_add_job(
cover.async_update_ha_state(True))
if hasattr(cover, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -24,7 +24,6 @@ from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -134,7 +133,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error("No covers added") _LOGGER.error("No covers added")
return False return False
async_add_devices(covers, True) async_add_devices(covers)
return True return True
@ -190,10 +189,6 @@ class CoverTemplate(CoverDevice):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._position = 100 if state.state == STATE_OPEN else 0
@callback @callback
def template_cover_state_listener(entity, old_state, new_state): def template_cover_state_listener(entity, old_state, new_state):
"""Handle target device state changes.""" """Handle target device state changes."""

View file

@ -215,20 +215,12 @@ def async_setup(hass, config: dict):
target_fans = component.async_extract_from_service(service) target_fans = component.async_extract_from_service(service)
params.pop(ATTR_ENTITY_ID, None) params.pop(ATTR_ENTITY_ID, None)
update_tasks = []
for fan in target_fans: for fan in target_fans:
yield from getattr(fan, method['method'])(**params) yield from getattr(fan, method['method'])(**params)
update_tasks = []
for fan in target_fans:
if not fan.should_poll: if not fan.should_poll:
continue continue
update_tasks.append(fan.async_update_ha_state(True))
update_coro = hass.async_add_job(fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -274,6 +274,7 @@ def async_setup(hass, config):
preprocess_turn_on_alternatives(params) preprocess_turn_on_alternatives(params)
update_tasks = []
for light in target_lights: for light in target_lights:
if service.service == SERVICE_TURN_ON: if service.service == SERVICE_TURN_ON:
yield from light.async_turn_on(**params) yield from light.async_turn_on(**params)
@ -282,18 +283,9 @@ def async_setup(hass, config):
else: else:
yield from light.async_toggle(**params) yield from light.async_toggle(**params)
update_tasks = []
for light in target_lights:
if not light.should_poll: if not light.should_poll:
continue continue
update_tasks.append(light.async_update_ha_state(True))
update_coro = hass.async_add_job(
light.async_update_ha_state(True))
if hasattr(light, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -20,7 +20,6 @@ from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -87,7 +86,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error("No lights added") _LOGGER.error("No lights added")
return False return False
async_add_devices(lights, True) async_add_devices(lights)
return True return True
@ -150,10 +149,6 @@ class LightTemplate(Light):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
@callback @callback
def template_light_state_listener(entity, old_state, new_state): def template_light_state_listener(entity, old_state, new_state):
"""Handle target device state changes.""" """Handle target device state changes."""
@ -207,6 +202,7 @@ class LightTemplate(Light):
@asyncio.coroutine @asyncio.coroutine
def async_update(self): def async_update(self):
"""Update the state from the template.""" """Update the state from the template."""
print("ASYNC UPDATE")
if self._template is not None: if self._template is not None:
try: try:
state = self._template.async_render().lower() state = self._template.async_render().lower()

View file

@ -90,24 +90,16 @@ def async_setup(hass, config):
code = service.data.get(ATTR_CODE) code = service.data.get(ATTR_CODE)
update_tasks = []
for entity in target_locks: for entity in target_locks:
if service.service == SERVICE_LOCK: if service.service == SERVICE_LOCK:
yield from entity.async_lock(code=code) yield from entity.async_lock(code=code)
else: else:
yield from entity.async_unlock(code=code) yield from entity.async_unlock(code=code)
update_tasks = []
for entity in target_locks:
if not entity.should_poll: if not entity.should_poll:
continue continue
update_tasks.append(entity.async_update_ha_state(True))
update_coro = hass.async_add_job(
entity.async_update_ha_state(True))
if hasattr(entity, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -406,16 +406,9 @@ def async_setup(hass, config):
update_tasks = [] update_tasks = []
for player in target_players: for player in target_players:
yield from getattr(player, method['method'])(**params) yield from getattr(player, method['method'])(**params)
for player in target_players:
if not player.should_poll: if not player.should_poll:
continue continue
update_tasks.append(player.async_update_ha_state(True))
update_coro = player.async_update_ha_state(True)
if hasattr(player, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -148,6 +148,7 @@ def async_setup(hass, config):
num_repeats = service.data.get(ATTR_NUM_REPEATS) num_repeats = service.data.get(ATTR_NUM_REPEATS)
delay_secs = service.data.get(ATTR_DELAY_SECS) delay_secs = service.data.get(ATTR_DELAY_SECS)
update_tasks = []
for remote in target_remotes: for remote in target_remotes:
if service.service == SERVICE_TURN_ON: if service.service == SERVICE_TURN_ON:
yield from remote.async_turn_on(activity=activity_id) yield from remote.async_turn_on(activity=activity_id)
@ -160,17 +161,9 @@ def async_setup(hass, config):
else: else:
yield from remote.async_turn_off(activity=activity_id) yield from remote.async_turn_off(activity=activity_id)
update_tasks = []
for remote in target_remotes:
if not remote.should_poll: if not remote.should_poll:
continue continue
update_tasks.append(remote.async_update_ha_state(True))
update_coro = hass.async_add_job(
remote.async_update_ha_state(True))
if hasattr(remote, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -19,7 +19,6 @@ from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -93,10 +92,6 @@ class SensorTemplate(Entity):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state
@callback @callback
def template_sensor_state_listener(entity, old_state, new_state): def template_sensor_state_listener(entity, old_state, new_state):
"""Handle device state changes.""" """Handle device state changes."""

View file

@ -107,6 +107,7 @@ def async_setup(hass, config):
"""Handle calls to the switch services.""" """Handle calls to the switch services."""
target_switches = component.async_extract_from_service(service) target_switches = component.async_extract_from_service(service)
update_tasks = []
for switch in target_switches: for switch in target_switches:
if service.service == SERVICE_TURN_ON: if service.service == SERVICE_TURN_ON:
yield from switch.async_turn_on() yield from switch.async_turn_on()
@ -115,17 +116,9 @@ def async_setup(hass, config):
else: else:
yield from switch.async_turn_off() yield from switch.async_turn_off()
update_tasks = []
for switch in target_switches:
if not switch.should_poll: if not switch.should_poll:
continue continue
update_tasks.append(switch.async_update_ha_state(True))
update_coro = hass.async_add_job(
switch.async_update_ha_state(True))
if hasattr(switch, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -19,7 +19,6 @@ from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -71,7 +70,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error("No switches added") _LOGGER.error("No switches added")
return False return False
async_add_devices(switches, True) async_add_devices(switches)
return True return True
@ -96,10 +95,6 @@ class SwitchTemplate(SwitchDevice):
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
@callback @callback
def template_switch_state_listener(entity, old_state, new_state): def template_switch_state_listener(entity, old_state, new_state):
"""Handle target device state changes.""" """Handle target device state changes."""

View file

@ -200,13 +200,7 @@ def async_setup(hass, config):
yield from getattr(vacuum, method['method'])(**params) yield from getattr(vacuum, method['method'])(**params)
if not vacuum.should_poll: if not vacuum.should_poll:
continue continue
update_tasks.append(vacuum.async_update_ha_state(True))
update_coro = hass.async_add_job(
vacuum.async_update_ha_state(True))
if hasattr(vacuum, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) yield from asyncio.wait(update_tasks, loop=hass.loop)

View file

@ -71,8 +71,11 @@ class Entity(object):
# If we reported if this entity was slow # If we reported if this entity was slow
_slow_reported = False _slow_reported = False
# protect for multiple updates # Protect for multiple updates
_update_warn = None _update_staged = False
# Process updates pararell
parallel_updates = None
@property @property
def should_poll(self) -> bool: def should_poll(self) -> bool:
@ -197,11 +200,15 @@ class Entity(object):
# update entity data # update entity data
if force_refresh: if force_refresh:
if self._update_warn: if self._update_staged:
# Update is already in progress.
return return
self._update_staged = True
self._update_warn = self.hass.loop.call_later( # Process update sequential
if self.parallel_updates:
yield from self.parallel_updates.acquire()
update_warn = self.hass.loop.call_later(
SLOW_UPDATE_WARNING, _LOGGER.warning, SLOW_UPDATE_WARNING, _LOGGER.warning,
"Update of %s is taking over %s seconds", self.entity_id, "Update of %s is taking over %s seconds", self.entity_id,
SLOW_UPDATE_WARNING SLOW_UPDATE_WARNING
@ -217,8 +224,10 @@ class Entity(object):
_LOGGER.exception("Update for %s fails", self.entity_id) _LOGGER.exception("Update for %s fails", self.entity_id)
return return
finally: finally:
self._update_warn.cancel() self._update_staged = False
self._update_warn = None update_warn.cancel()
if self.parallel_updates:
self.parallel_updates.release()
start = timer() start = timer()

View file

@ -44,7 +44,7 @@ class EntityComponent(object):
self.config = None self.config = None
self._platforms = { self._platforms = {
'core': EntityPlatform(self, domain, self.scan_interval, None), 'core': EntityPlatform(self, domain, self.scan_interval, 0, None),
} }
self.async_add_entities = self._platforms['core'].async_add_entities self.async_add_entities = self._platforms['core'].async_add_entities
self.add_entities = self._platforms['core'].add_entities self.add_entities = self._platforms['core'].add_entities
@ -128,16 +128,22 @@ class EntityComponent(object):
return return
# Config > Platform > Component # Config > Platform > Component
scan_interval = (platform_config.get(CONF_SCAN_INTERVAL) or scan_interval = (
getattr(platform, 'SCAN_INTERVAL', None) or platform_config.get(CONF_SCAN_INTERVAL) or
self.scan_interval) getattr(platform, 'SCAN_INTERVAL', None) or self.scan_interval)
parallel_updates = getattr(
platform, 'PARALLEL_UPDATES',
int(not hasattr(platform, 'async_setup_platform')))
entity_namespace = platform_config.get(CONF_ENTITY_NAMESPACE) entity_namespace = platform_config.get(CONF_ENTITY_NAMESPACE)
key = (platform_type, scan_interval, entity_namespace) key = (platform_type, scan_interval, entity_namespace)
if key not in self._platforms: if key not in self._platforms:
self._platforms[key] = EntityPlatform( entity_platform = self._platforms[key] = EntityPlatform(
self, platform_type, scan_interval, entity_namespace) self, platform_type, scan_interval, parallel_updates,
entity_namespace)
else:
entity_platform = self._platforms[key] entity_platform = self._platforms[key]
self.logger.info("Setting up %s.%s", self.domain, platform_type) self.logger.info("Setting up %s.%s", self.domain, platform_type)
@ -204,13 +210,6 @@ class EntityComponent(object):
entity.hass = self.hass entity.hass = self.hass
# update/init entity data
if update_before_add:
if hasattr(entity, 'async_update'):
yield from entity.async_update()
else:
yield from self.hass.async_add_job(entity.update)
if getattr(entity, 'entity_id', None) is None: if getattr(entity, 'entity_id', None) is None:
object_id = entity.name or DEVICE_DEFAULT_NAME object_id = entity.name or DEVICE_DEFAULT_NAME
@ -235,7 +234,7 @@ class EntityComponent(object):
if hasattr(entity, 'async_added_to_hass'): if hasattr(entity, 'async_added_to_hass'):
yield from entity.async_added_to_hass() yield from entity.async_added_to_hass()
yield from entity.async_update_ha_state() yield from entity.async_update_ha_state(update_before_add)
return True return True
@ -316,17 +315,23 @@ class EntityComponent(object):
class EntityPlatform(object): class EntityPlatform(object):
"""Keep track of entities for a single platform and stay in loop.""" """Keep track of entities for a single platform and stay in loop."""
def __init__(self, component, platform, scan_interval, entity_namespace): def __init__(self, component, platform, scan_interval, parallel_updates,
entity_namespace):
"""Initialize the entity platform.""" """Initialize the entity platform."""
self.component = component self.component = component
self.platform = platform self.platform = platform
self.scan_interval = scan_interval self.scan_interval = scan_interval
self.parallel_updates = None
self.entity_namespace = entity_namespace self.entity_namespace = entity_namespace
self.platform_entities = [] self.platform_entities = []
self._tasks = [] self._tasks = []
self._async_unsub_polling = None self._async_unsub_polling = None
self._process_updates = asyncio.Lock(loop=component.hass.loop) self._process_updates = asyncio.Lock(loop=component.hass.loop)
if parallel_updates:
self.parallel_updates = asyncio.Semaphore(
parallel_updates, loop=component.hass.loop)
@asyncio.coroutine @asyncio.coroutine
def async_block_entities_done(self): def async_block_entities_done(self):
"""Wait until all entities add to hass.""" """Wait until all entities add to hass."""
@ -377,6 +382,7 @@ class EntityPlatform(object):
@asyncio.coroutine @asyncio.coroutine
def async_process_entity(new_entity): def async_process_entity(new_entity):
"""Add entities to StateMachine.""" """Add entities to StateMachine."""
new_entity.parallel_updates = self.parallel_updates
ret = yield from self.component.async_add_entity( ret = yield from self.component.async_add_entity(
new_entity, self, update_before_add=update_before_add new_entity, self, update_before_add=update_before_add
) )
@ -432,26 +438,10 @@ class EntityPlatform(object):
with (yield from self._process_updates): with (yield from self._process_updates):
tasks = [] tasks = []
to_update = []
for entity in self.platform_entities: for entity in self.platform_entities:
if not entity.should_poll: if not entity.should_poll:
continue continue
tasks.append(entity.async_update_ha_state(True))
update_coro = entity.async_update_ha_state(True)
if hasattr(entity, 'async_update'):
tasks.append(
self.component.hass.async_add_job(update_coro))
else:
to_update.append(update_coro)
for update_coro in to_update:
try:
yield from update_coro
except Exception: # pylint: disable=broad-except
self.component.logger.exception(
"Error while update entity from %s in %s",
self.platform, self.component.domain)
if tasks: if tasks:
yield from asyncio.wait(tasks, loop=self.component.hass.loop) yield from asyncio.wait(tasks, loop=self.component.hass.loop)

View file

@ -4,7 +4,6 @@ from datetime import timedelta
import unittest import unittest
from unittest import mock from unittest import mock
from homeassistant.core import CoreState, State
from homeassistant.const import MATCH_ALL from homeassistant.const import MATCH_ALL
from homeassistant import setup from homeassistant import setup
from homeassistant.components.binary_sensor import template from homeassistant.components.binary_sensor import template
@ -12,11 +11,9 @@ 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 homeassistant.util.async import run_callback_threadsafe
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE
from tests.common import ( from tests.common import (
get_test_home_assistant, assert_setup_component, mock_component, get_test_home_assistant, assert_setup_component, async_fire_time_changed)
async_fire_time_changed)
class TestBinarySensorTemplate(unittest.TestCase): class TestBinarySensorTemplate(unittest.TestCase):
@ -169,41 +166,6 @@ class TestBinarySensorTemplate(unittest.TestCase):
run_callback_threadsafe(self.hass.loop, vs.async_check_state).result() run_callback_threadsafe(self.hass.loop, vs.async_check_state).result()
@asyncio.coroutine
def test_restore_state(hass):
"""Ensure states are restored on startup."""
hass.data[DATA_RESTORE_CACHE] = {
'binary_sensor.test': State('binary_sensor.test', 'on'),
}
hass.state = CoreState.starting
mock_component(hass, 'recorder')
config = {
'binary_sensor': {
'platform': 'template',
'sensors': {
'test': {
'friendly_name': 'virtual thingy',
'value_template':
"{{ states.sensor.test_state.state == 'on' }}",
'device_class': 'motion',
},
},
},
}
yield from setup.async_setup_component(hass, 'binary_sensor', config)
state = hass.states.get('binary_sensor.test')
assert state.state == 'on'
yield from hass.async_start()
yield from hass.async_block_till_done()
state = hass.states.get('binary_sensor.test')
assert state.state == 'off'
@asyncio.coroutine @asyncio.coroutine
def test_template_delay_on(hass): def test_template_delay_on(hass):
"""Test binary sensor template delay on.""" """Test binary sensor template delay on."""

View file

@ -1,16 +1,14 @@
"""The tests for the Template light platform.""" """The tests for the Template light platform."""
import logging import logging
import asyncio
from homeassistant.core import callback, State, CoreState from homeassistant.core import callback
from homeassistant import setup from homeassistant import setup
import homeassistant.components as core import homeassistant.components as core
from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE
from tests.common import ( from tests.common import (
get_test_home_assistant, assert_setup_component, mock_component) get_test_home_assistant, assert_setup_component)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -627,49 +625,3 @@ class TestTemplateLight:
assert state is not None assert state is not None
assert state.attributes.get('friendly_name') == 'Template light' assert state.attributes.get('friendly_name') == 'Template light'
@asyncio.coroutine
def test_restore_state(hass):
"""Ensure states are restored on startup."""
hass.data[DATA_RESTORE_CACHE] = {
'light.test_template_light':
State('light.test_template_light', 'on'),
}
hass.state = CoreState.starting
mock_component(hass, 'recorder')
yield from setup.async_setup_component(hass, 'light', {
'light': {
'platform': 'template',
'lights': {
'test_template_light': {
'value_template':
"{{states.light.test_state.state}}",
'turn_on': {
'service': 'test.automation',
},
'turn_off': {
'service': 'light.turn_off',
'entity_id': 'light.test_state'
},
'set_level': {
'service': 'test.automation',
'data_template': {
'entity_id': 'light.test_state',
'brightness': '{{brightness}}'
}
}
}
}
}
})
state = hass.states.get('light.test_template_light')
assert state.state == 'on'
yield from hass.async_start()
yield from hass.async_block_till_done()
state = hass.states.get('light.test_template_light')
assert state.state == 'off'

View file

@ -1,12 +1,7 @@
"""The test for the Template sensor platform.""" """The test for the Template sensor platform."""
import asyncio from homeassistant.setup import setup_component
from homeassistant.core import CoreState, State from tests.common import get_test_home_assistant, assert_setup_component
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE
from tests.common import (
get_test_home_assistant, assert_setup_component, mock_component)
class TestTemplateSensor: class TestTemplateSensor:
@ -188,36 +183,3 @@ class TestTemplateSensor:
self.hass.block_till_done() self.hass.block_till_done()
assert self.hass.states.all() == [] assert self.hass.states.all() == []
@asyncio.coroutine
def test_restore_state(hass):
"""Ensure states are restored on startup."""
hass.data[DATA_RESTORE_CACHE] = {
'sensor.test_template_sensor':
State('sensor.test_template_sensor', 'It Test.'),
}
hass.state = CoreState.starting
mock_component(hass, 'recorder')
yield from async_setup_component(hass, 'sensor', {
'sensor': {
'platform': 'template',
'sensors': {
'test_template_sensor': {
'value_template':
"It {{ states.sensor.test_state.state }}."
}
}
}
})
state = hass.states.get('sensor.test_template_sensor')
assert state.state == 'It Test.'
yield from hass.async_start()
yield from hass.async_block_till_done()
state = hass.states.get('sensor.test_template_sensor')
assert state.state == 'It .'

View file

@ -1,14 +1,11 @@
"""The tests for the Template switch platform.""" """The tests for the Template switch platform."""
import asyncio from homeassistant.core import callback
from homeassistant.core import callback, State, CoreState
from homeassistant import setup from homeassistant import setup
import homeassistant.components as core import homeassistant.components as core
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.restore_state import DATA_RESTORE_CACHE
from tests.common import ( from tests.common import (
get_test_home_assistant, assert_setup_component, mock_component) get_test_home_assistant, assert_setup_component)
class TestTemplateSwitch: class TestTemplateSwitch:
@ -410,44 +407,3 @@ class TestTemplateSwitch:
self.hass.block_till_done() self.hass.block_till_done()
assert len(self.calls) == 1 assert len(self.calls) == 1
@asyncio.coroutine
def test_restore_state(hass):
"""Ensure states are restored on startup."""
hass.data[DATA_RESTORE_CACHE] = {
'switch.test_template_switch':
State('switch.test_template_switch', 'on'),
}
hass.state = CoreState.starting
mock_component(hass, 'recorder')
yield from setup.async_setup_component(hass, 'switch', {
'switch': {
'platform': 'template',
'switches': {
'test_template_switch': {
'value_template':
"{{ states.switch.test_state.state }}",
'turn_on': {
'service': 'switch.turn_on',
'entity_id': 'switch.test_state'
},
'turn_off': {
'service': 'switch.turn_off',
'entity_id': 'switch.test_state'
},
}
}
}
})
state = hass.states.get('switch.test_template_switch')
assert state.state == 'on'
yield from hass.async_start()
yield from hass.async_block_till_done()
state = hass.states.get('switch.test_template_switch')
assert state.state == 'unavailable'

View file

@ -213,3 +213,162 @@ def test_async_schedule_update_ha_state(hass):
yield from hass.async_block_till_done() yield from hass.async_block_till_done()
assert update_call is True assert update_call is True
@asyncio.coroutine
def test_async_pararell_updates_with_zero(hass):
"""Test pararell updates with 0 (disabled)."""
updates = []
test_lock = asyncio.Event(loop=hass.loop)
class AsyncEntity(entity.Entity):
def __init__(self, entity_id, count):
"""Initialize Async test entity."""
self.entity_id = entity_id
self.hass = hass
self._count = count
@asyncio.coroutine
def async_update(self):
"""Test update."""
updates.append(self._count)
yield from test_lock.wait()
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_2 = AsyncEntity("sensor.test_2", 2)
ent_1.async_schedule_update_ha_state(True)
ent_2.async_schedule_update_ha_state(True)
while True:
if len(updates) == 2:
break
yield from asyncio.sleep(0, loop=hass.loop)
assert len(updates) == 2
assert updates == [1, 2]
test_lock.set()
@asyncio.coroutine
def test_async_pararell_updates_with_one(hass):
"""Test pararell updates with 1 (sequential)."""
updates = []
test_lock = asyncio.Lock(loop=hass.loop)
test_semephore = asyncio.Semaphore(1, loop=hass.loop)
yield from test_lock.acquire()
class AsyncEntity(entity.Entity):
def __init__(self, entity_id, count):
"""Initialize Async test entity."""
self.entity_id = entity_id
self.hass = hass
self._count = count
self.parallel_updates = test_semephore
@asyncio.coroutine
def async_update(self):
"""Test update."""
updates.append(self._count)
yield from test_lock.acquire()
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_2 = AsyncEntity("sensor.test_2", 2)
ent_3 = AsyncEntity("sensor.test_3", 3)
ent_1.async_schedule_update_ha_state(True)
ent_2.async_schedule_update_ha_state(True)
ent_3.async_schedule_update_ha_state(True)
while True:
if len(updates) == 1:
break
yield from asyncio.sleep(0, loop=hass.loop)
assert len(updates) == 1
assert updates == [1]
test_lock.release()
while True:
if len(updates) == 2:
break
yield from asyncio.sleep(0, loop=hass.loop)
assert len(updates) == 2
assert updates == [1, 2]
test_lock.release()
while True:
if len(updates) == 3:
break
yield from asyncio.sleep(0, loop=hass.loop)
assert len(updates) == 3
assert updates == [1, 2, 3]
test_lock.release()
@asyncio.coroutine
def test_async_pararell_updates_with_two(hass):
"""Test pararell updates with 2 (pararell)."""
updates = []
test_lock = asyncio.Lock(loop=hass.loop)
test_semephore = asyncio.Semaphore(2, loop=hass.loop)
yield from test_lock.acquire()
class AsyncEntity(entity.Entity):
def __init__(self, entity_id, count):
"""Initialize Async test entity."""
self.entity_id = entity_id
self.hass = hass
self._count = count
self.parallel_updates = test_semephore
@asyncio.coroutine
def async_update(self):
"""Test update."""
updates.append(self._count)
yield from test_lock.acquire()
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_2 = AsyncEntity("sensor.test_2", 2)
ent_3 = AsyncEntity("sensor.test_3", 3)
ent_4 = AsyncEntity("sensor.test_4", 4)
ent_1.async_schedule_update_ha_state(True)
ent_2.async_schedule_update_ha_state(True)
ent_3.async_schedule_update_ha_state(True)
ent_4.async_schedule_update_ha_state(True)
while True:
if len(updates) == 2:
break
yield from asyncio.sleep(0, loop=hass.loop)
assert len(updates) == 2
assert updates == [1, 2]
test_lock.release()
yield from asyncio.sleep(0, loop=hass.loop)
test_lock.release()
while True:
if len(updates) == 4:
break
yield from asyncio.sleep(0, loop=hass.loop)
assert len(updates) == 4
assert updates == [1, 2, 3, 4]
test_lock.release()
yield from asyncio.sleep(0, loop=hass.loop)
test_lock.release()

View file

@ -578,3 +578,79 @@ def test_platform_not_ready(hass):
yield from hass.async_block_till_done() yield from hass.async_block_till_done()
assert len(platform1_setup.mock_calls) == 3 assert len(platform1_setup.mock_calls) == 3
assert 'test_domain.mod1' in hass.config.components assert 'test_domain.mod1' in hass.config.components
@asyncio.coroutine
def test_pararell_updates_async_platform(hass):
"""Warn we log when platform setup takes a long time."""
platform = MockPlatform()
@asyncio.coroutine
def mock_update(*args, **kwargs):
pass
platform.async_setup_platform = mock_update
loader.set_component('test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
component._platforms = {}
yield from component.async_setup({
DOMAIN: {
'platform': 'platform',
}
})
handle = list(component._platforms.values())[-1]
assert handle.parallel_updates is None
@asyncio.coroutine
def test_pararell_updates_async_platform_with_constant(hass):
"""Warn we log when platform setup takes a long time."""
platform = MockPlatform()
@asyncio.coroutine
def mock_update(*args, **kwargs):
pass
platform.async_setup_platform = mock_update
platform.PARALLEL_UPDATES = 1
loader.set_component('test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
component._platforms = {}
yield from component.async_setup({
DOMAIN: {
'platform': 'platform',
}
})
handle = list(component._platforms.values())[-1]
assert handle.parallel_updates is not None
@asyncio.coroutine
def test_pararell_updates_sync_platform(hass):
"""Warn we log when platform setup takes a long time."""
platform = MockPlatform()
loader.set_component('test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
component._platforms = {}
yield from component.async_setup({
DOMAIN: {
'platform': 'platform',
}
})
handle = list(component._platforms.values())[-1]
assert handle.parallel_updates is not None