From 41aaeb715a99eeb4f5d6ecf9c50aaaa154f1ef27 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 16 Nov 2016 06:06:50 +0100 Subject: [PATCH] Convert switch to AsnycIO (#4382) * Convert switch to AsnycIO * Move update entity to service * use time better for faster handling * Change to suggestion from paulus * Use new shedule_update_ha_state * fix lint * minimize executor calls --- homeassistant/components/switch/__init__.py | 51 ++++++++++++------- .../components/switch/command_line.py | 4 +- homeassistant/components/switch/demo.py | 4 +- homeassistant/components/switch/enocean.py | 2 +- homeassistant/components/switch/flux.py | 4 +- homeassistant/components/switch/knx.py | 4 +- homeassistant/components/switch/mqtt.py | 4 +- homeassistant/components/switch/mysensors.py | 8 +-- homeassistant/components/switch/netio.py | 2 +- homeassistant/components/switch/pilight.py | 4 +- .../components/switch/pulseaudio_loopback.py | 4 +- homeassistant/components/switch/rpi_gpio.py | 4 +- homeassistant/components/switch/scsgate.py | 4 +- homeassistant/components/switch/template.py | 2 +- homeassistant/components/switch/vera.py | 4 +- .../components/switch/wake_on_lan.py | 2 - homeassistant/components/switch/wemo.py | 4 +- homeassistant/helpers/entity.py | 25 ++++++--- 18 files changed, 80 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 1f92b458d53..02a313c675f 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -4,6 +4,7 @@ Component to interface with various switches that can be controlled remotely. For more details about this component, please refer to the documentation at https://home-assistant.io/components/switch/ """ +import asyncio from datetime import timedelta import logging import os @@ -69,38 +70,50 @@ def toggle(hass, entity_id=None): hass.services.call(DOMAIN, SERVICE_TOGGLE, data) -def setup(hass, config): +@asyncio.coroutine +def async_setup(hass, config): """Track states and offer events for switches.""" component = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_SWITCHES) - component.setup(config) + yield from component.async_setup(config) - def handle_switch_service(service): + @asyncio.coroutine + def async_handle_switch_service(service): """Handle calls to the switch services.""" - target_switches = component.extract_from_service(service) + target_switches = component.async_extract_from_service(service) + update_tasks = [] for switch in target_switches: if service.service == SERVICE_TURN_ON: - switch.turn_on() + yield from switch.async_turn_on() elif service.service == SERVICE_TOGGLE: - switch.toggle() + yield from switch.async_toggle() else: - switch.turn_off() + yield from switch.async_turn_off() if switch.should_poll: - switch.update_ha_state(True) + update_coro = switch.async_update_ha_state(True) + if hasattr(switch, 'async_update'): + update_tasks.append(hass.loop.create_task(update_coro)) + else: + yield from update_coro - descriptions = load_yaml_config_file( - os.path.join(os.path.dirname(__file__), 'services.yaml')) - hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service, - descriptions.get(SERVICE_TURN_OFF), - schema=SWITCH_SERVICE_SCHEMA) - hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service, - descriptions.get(SERVICE_TURN_ON), - schema=SWITCH_SERVICE_SCHEMA) - hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_switch_service, - descriptions.get(SERVICE_TOGGLE), - schema=SWITCH_SERVICE_SCHEMA) + if update_tasks: + yield from asyncio.wait(update_tasks, loop=hass.loop) + + descriptions = yield from hass.loop.run_in_executor( + None, load_yaml_config_file, os.path.join( + os.path.dirname(__file__), 'services.yaml')) + + hass.services.async_register( + DOMAIN, SERVICE_TURN_OFF, async_handle_switch_service, + descriptions.get(SERVICE_TURN_OFF), schema=SWITCH_SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_TURN_ON, async_handle_switch_service, + descriptions.get(SERVICE_TURN_ON), schema=SWITCH_SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_TOGGLE, async_handle_switch_service, + descriptions.get(SERVICE_TOGGLE), schema=SWITCH_SERVICE_SCHEMA) return True diff --git a/homeassistant/components/switch/command_line.py b/homeassistant/components/switch/command_line.py index 1ea108559e6..0fe804d71a3 100644 --- a/homeassistant/components/switch/command_line.py +++ b/homeassistant/components/switch/command_line.py @@ -149,11 +149,11 @@ class CommandSwitch(SwitchDevice): if (CommandSwitch._switch(self._command_on) and not self._command_state): self._state = True - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" if (CommandSwitch._switch(self._command_off) and not self._command_state): self._state = False - self.update_ha_state() + self.shedule_update_ha_state() diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/switch/demo.py index f867473d441..bc5e90cefb4 100644 --- a/homeassistant/components/switch/demo.py +++ b/homeassistant/components/switch/demo.py @@ -66,9 +66,9 @@ class DemoSwitch(SwitchDevice): def turn_on(self, **kwargs): """Turn the switch on.""" self._state = True - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" self._state = False - self.update_ha_state() + self.shedule_update_ha_state() diff --git a/homeassistant/components/switch/enocean.py b/homeassistant/components/switch/enocean.py index 87a89d148ab..84c126076e3 100644 --- a/homeassistant/components/switch/enocean.py +++ b/homeassistant/components/switch/enocean.py @@ -79,4 +79,4 @@ class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity): def value_changed(self, val): """Update the internal state of the switch.""" self._on_state = val - self.update_ha_state() + self.shedule_update_ha_state() diff --git a/homeassistant/components/switch/flux.py b/homeassistant/components/switch/flux.py index bd226ac087a..a781d72fa77 100644 --- a/homeassistant/components/switch/flux.py +++ b/homeassistant/components/switch/flux.py @@ -137,7 +137,7 @@ class FluxSwitch(SwitchDevice): self._state = True self.unsub_tracker = track_utc_time_change(self.hass, self.flux_update, second=[0, 30]) - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self, **kwargs): """Turn off flux.""" @@ -146,7 +146,7 @@ class FluxSwitch(SwitchDevice): self.unsub_tracker = None self._state = False - self.update_ha_state() + self.shedule_update_ha_state() def flux_update(self, now=None): """Update all the lights using flux.""" diff --git a/homeassistant/components/switch/knx.py b/homeassistant/components/switch/knx.py index e290f3ba4e1..1e02fd35844 100644 --- a/homeassistant/components/switch/knx.py +++ b/homeassistant/components/switch/knx.py @@ -40,7 +40,7 @@ class KNXSwitch(KNXGroupAddress, SwitchDevice): self.group_write(1) self._state = [1] if not self.should_poll: - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self, **kwargs): """Turn the switch off. @@ -50,4 +50,4 @@ class KNXSwitch(KNXGroupAddress, SwitchDevice): self.group_write(0) self._state = [0] if not self.should_poll: - self.update_ha_state() + self.shedule_update_ha_state() diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 2283b8539ba..76ba9bf28bc 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -117,7 +117,7 @@ class MqttSwitch(SwitchDevice): if self._optimistic: # Optimistically assume that switch has changed state. self._state = True - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" @@ -126,4 +126,4 @@ class MqttSwitch(SwitchDevice): if self._optimistic: # Optimistically assume that switch has changed state. self._state = False - self.update_ha_state() + self.shedule_update_ha_state() diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 6c26a79f21a..04282a4fa66 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -137,7 +137,7 @@ class MySensorsSwitch(mysensors.MySensorsDeviceEntity, SwitchDevice): if self.gateway.optimistic: # optimistically assume that switch has changed state self._values[self.value_type] = STATE_ON - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self): """Turn the switch off.""" @@ -146,7 +146,7 @@ class MySensorsSwitch(mysensors.MySensorsDeviceEntity, SwitchDevice): if self.gateway.optimistic: # optimistically assume that switch has changed state self._values[self.value_type] = STATE_OFF - self.update_ha_state() + self.shedule_update_ha_state() class MySensorsIRSwitch(MySensorsSwitch): @@ -182,7 +182,7 @@ class MySensorsIRSwitch(MySensorsSwitch): # optimistically assume that switch has changed state self._values[self.value_type] = self._ir_code self._values[set_req.V_LIGHT] = STATE_ON - self.update_ha_state() + self.shedule_update_ha_state() # turn off switch after switch was turned on self.turn_off() @@ -198,7 +198,7 @@ class MySensorsIRSwitch(MySensorsSwitch): if self.gateway.optimistic: # optimistically assume that switch has changed state self._values[set_req.V_LIGHT] = STATE_OFF - self.update_ha_state() + self.shedule_update_ha_state() def update(self): """Update the controller with the latest value from a sensor.""" diff --git a/homeassistant/components/switch/netio.py b/homeassistant/components/switch/netio.py index 9d292cc1b9a..151a1001e38 100644 --- a/homeassistant/components/switch/netio.py +++ b/homeassistant/components/switch/netio.py @@ -156,7 +156,7 @@ class NetioSwitch(SwitchDevice): val[self.outlet - 1] = '1' if value else '0' self.netio.get('port list %s' % ''.join(val)) self.netio.states[self.outlet - 1] = value - self.update_ha_state() + self.shedule_update_ha_state() @property def is_on(self): diff --git a/homeassistant/components/switch/pilight.py b/homeassistant/components/switch/pilight.py index 80a36756d79..2f0e81c753d 100644 --- a/homeassistant/components/switch/pilight.py +++ b/homeassistant/components/switch/pilight.py @@ -127,11 +127,11 @@ class PilightSwitch(SwitchDevice): self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME, self._code_on, blocking=True) self._state = True - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self): """Turn the switch on by calling pilight.send service with off code.""" self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME, self._code_off, blocking=True) self._state = False - self.update_ha_state() + self.shedule_update_ha_state() diff --git a/homeassistant/components/switch/pulseaudio_loopback.py b/homeassistant/components/switch/pulseaudio_loopback.py index 250111ecfb5..b8584ab232b 100644 --- a/homeassistant/components/switch/pulseaudio_loopback.py +++ b/homeassistant/components/switch/pulseaudio_loopback.py @@ -170,7 +170,7 @@ class PALoopbackSwitch(SwitchDevice): self._pa_svr.update_module_state(no_throttle=True) self._module_idx = self._pa_svr.get_module_idx( self._sink_name, self._source_name) - self.update_ha_state() + self.shedule_update_ha_state() else: _LOGGER.warning(IGNORED_SWITCH_WARN) @@ -181,7 +181,7 @@ class PALoopbackSwitch(SwitchDevice): self._pa_svr.update_module_state(no_throttle=True) self._module_idx = self._pa_svr.get_module_idx( self._sink_name, self._source_name) - self.update_ha_state() + self.shedule_update_ha_state() else: _LOGGER.warning(IGNORED_SWITCH_WARN) diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index c6400432aa2..a08e6f86242 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -77,10 +77,10 @@ class RPiGPIOSwitch(ToggleEntity): """Turn the device on.""" rpi_gpio.write_output(self._port, 0 if self._invert_logic else 1) self._state = True - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self): """Turn the device off.""" rpi_gpio.write_output(self._port, 1 if self._invert_logic else 0) self._state = False - self.update_ha_state() + self.shedule_update_ha_state() diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index c1d5b19fdee..551704d032f 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -119,7 +119,7 @@ class SCSGateSwitch(SwitchDevice): ToggleStatusTask(target=self._scs_id, toggled=True)) self._toggled = True - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" @@ -129,7 +129,7 @@ class SCSGateSwitch(SwitchDevice): ToggleStatusTask(target=self._scs_id, toggled=False)) self._toggled = False - self.update_ha_state() + self.shedule_update_ha_state() def process_event(self, message): """Handle a SCSGate message related with this switch.""" diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index 83bd1aff9b1..f17d95b21b3 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -92,7 +92,7 @@ class SwitchTemplate(SwitchDevice): @callback def template_switch_state_listener(entity, old_state, new_state): """Called when the target device changes state.""" - hass.async_add_job(self.async_update_ha_state, True) + hass.async_add_job(self.async_update_ha_state(True)) async_track_state_change( hass, entity_ids, template_switch_state_listener) diff --git a/homeassistant/components/switch/vera.py b/homeassistant/components/switch/vera.py index a8b360e2339..21d386ddadb 100644 --- a/homeassistant/components/switch/vera.py +++ b/homeassistant/components/switch/vera.py @@ -36,13 +36,13 @@ class VeraSwitch(VeraDevice, SwitchDevice): """Turn device on.""" self.vera_device.switch_on() self._state = STATE_ON - self.update_ha_state() + self.shedule_update_ha_state() def turn_off(self, **kwargs): """Turn device off.""" self.vera_device.switch_off() self._state = STATE_OFF - self.update_ha_state() + self.shedule_update_ha_state() @property def current_power_mwh(self): diff --git a/homeassistant/components/switch/wake_on_lan.py b/homeassistant/components/switch/wake_on_lan.py index 29f9ca5096a..e6efc1869af 100644 --- a/homeassistant/components/switch/wake_on_lan.py +++ b/homeassistant/components/switch/wake_on_lan.py @@ -76,13 +76,11 @@ class WOLSwitch(SwitchDevice): def turn_on(self): """Turn the device on.""" self._wol.send_magic_packet(self._mac_address) - self.update_ha_state() def turn_off(self): """Turn the device off if an off action is present.""" if self._off_script is not None: self._off_script.run() - self.update_ha_state() def update(self): """Check if device is on and update the state.""" diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 63b2665449e..986d443fa37 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -150,14 +150,14 @@ class WemoSwitch(SwitchDevice): def turn_on(self, **kwargs): """Turn the switch on.""" self._state = WEMO_ON - self.update_ha_state() self.wemo.on() + self.shedule_update_ha_state() def turn_off(self): """Turn the switch off.""" self._state = WEMO_OFF - self.update_ha_state() self.wemo.off() + self.shedule_update_ha_state() def update(self): """Update WeMo state.""" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index ac058f89143..8556d028062 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -1,6 +1,7 @@ """An abstract class for entities.""" import asyncio import logging +import functools as ft from timeit import default_timer as timer from typing import Any, Optional, List, Dict @@ -269,6 +270,18 @@ class Entity(object): self.hass.states.async_set( self.entity_id, state, attr, self.force_update) + def shedule_update_ha_state(self, force_refresh=False): + """Shedule a update ha state change task. + + That is only needed on executor to not block. + """ + # We're already in a thread, do the force refresh here. + if force_refresh and not hasattr(self, 'async_update'): + self.update() + force_refresh = False + + self.hass.add_job(self.async_update_ha_state(force_refresh)) + def remove(self) -> None: """Remove entitiy from HASS.""" run_coroutine_threadsafe( @@ -324,23 +337,23 @@ class ToggleEntity(Entity): def turn_on(self, **kwargs) -> None: """Turn the entity on.""" - run_coroutine_threadsafe(self.async_turn_on(**kwargs), - self.hass.loop).result() + raise NotImplementedError() @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the entity on.""" - raise NotImplementedError() + yield from self.hass.loop.run_in_executor( + None, ft.partial(self.turn_on, **kwargs)) def turn_off(self, **kwargs) -> None: """Turn the entity off.""" - run_coroutine_threadsafe(self.async_turn_off(**kwargs), - self.hass.loop).result() + raise NotImplementedError() @asyncio.coroutine def async_turn_off(self, **kwargs): """Turn the entity off.""" - raise NotImplementedError() + yield from self.hass.loop.run_in_executor( + None, ft.partial(self.turn_off, **kwargs)) def toggle(self) -> None: """Toggle the entity."""