From bde7176b3c9e4059be3cbe8be7a91377b902eb82 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 30 Nov 2016 22:33:38 +0100 Subject: [PATCH] Migrate light component to async (#4635) --- homeassistant/components/light/__init__.py | 146 ++++++++++-------- homeassistant/components/light/demo.py | 4 +- .../components/light/limitlessled.py | 2 +- homeassistant/components/light/mqtt.py | 4 +- homeassistant/components/light/mqtt_json.py | 4 +- .../components/light/mqtt_template.py | 4 +- homeassistant/components/light/mysensors.py | 8 +- .../components/light/osramlightify.py | 4 +- homeassistant/components/light/scsgate.py | 4 +- homeassistant/components/light/vera.py | 4 +- tests/components/light/test_init.py | 3 +- 11 files changed, 99 insertions(+), 88 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index c4ed91af0af..ff7121feaaa 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -4,6 +4,7 @@ Provides functionality to interact with lights. For more details about this component, please refer to the documentation at https://home-assistant.io/components/light/ """ +import asyncio import logging import os import csv @@ -163,7 +164,7 @@ def async_turn_on(hass, entity_id=None, transition=None, brightness=None, ] if value is not None } - hass.async_add_job(hass.services.async_call, DOMAIN, SERVICE_TURN_ON, data) + hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)) def turn_off(hass, entity_id=None, transition=None): @@ -182,8 +183,8 @@ def async_turn_off(hass, entity_id=None, transition=None): ] if value is not None } - hass.async_add_job(hass.services.async_call, DOMAIN, SERVICE_TURN_OFF, - data) + hass.async_add_job(hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, data)) def toggle(hass, entity_id=None, transition=None): @@ -198,13 +199,83 @@ def toggle(hass, entity_id=None, transition=None): hass.services.call(DOMAIN, SERVICE_TOGGLE, data) -def setup(hass, config): +@asyncio.coroutine +def async_setup(hass, config): """Expose light control via statemachine and services.""" component = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LIGHTS) - component.setup(config) + yield from component.async_setup(config) - # Load built-in profiles and custom profiles + # load profiles from files + profiles = yield from hass.loop.run_in_executor( + None, _load_profile_data, hass) + + if profiles is None: + return False + + @asyncio.coroutine + def async_handle_light_service(service): + """Hande a turn light on or off service call.""" + # Get the validated data + params = service.data.copy() + + # Convert the entity ids to valid light ids + target_lights = component.async_extract_from_service(service) + params.pop(ATTR_ENTITY_ID, None) + + # Processing extra data for turn light on request. + profile = profiles.get(params.pop(ATTR_PROFILE, None)) + + if profile: + params.setdefault(ATTR_XY_COLOR, profile[:2]) + params.setdefault(ATTR_BRIGHTNESS, profile[2]) + + color_name = params.pop(ATTR_COLOR_NAME, None) + + if color_name is not None: + params[ATTR_RGB_COLOR] = color_util.color_name_to_rgb(color_name) + + update_tasks = [] + for light in target_lights: + if service.service == SERVICE_TURN_ON: + yield from light.async_turn_on(**params) + elif service.service == SERVICE_TURN_OFF: + yield from light.async_turn_off(**params) + else: + yield from light.async_toggle(**params) + + if light.should_poll: + update_coro = light.async_update_ha_state(True) + if hasattr(light, 'async_update'): + update_tasks.append(hass.loop.create_task(update_coro)) + else: + yield from update_coro + + if update_tasks: + yield from asyncio.wait(update_tasks, loop=hass.loop) + + # Listen for light on and light off service calls. + 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_ON, async_handle_light_service, + descriptions.get(SERVICE_TURN_ON), schema=LIGHT_TURN_ON_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_TURN_OFF, async_handle_light_service, + descriptions.get(SERVICE_TURN_OFF), schema=LIGHT_TURN_OFF_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_TOGGLE, async_handle_light_service, + descriptions.get(SERVICE_TOGGLE), schema=LIGHT_TOGGLE_SCHEMA) + + return True + + +def _load_profile_data(hass): + """Load built-in profiles and custom profiles.""" profile_paths = [os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), hass.config.path(LIGHT_PROFILES_FILE)] @@ -226,67 +297,8 @@ def setup(hass, config): except vol.MultipleInvalid as ex: _LOGGER.error("Error parsing light profile from %s: %s", profile_path, ex) - return False - - def handle_light_service(service): - """Hande a turn light on or off service call.""" - # Get the validated data - params = service.data.copy() - - # Convert the entity ids to valid light ids - target_lights = component.extract_from_service(service) - params.pop(ATTR_ENTITY_ID, None) - - service_fun = None - if service.service == SERVICE_TURN_OFF: - service_fun = 'turn_off' - elif service.service == SERVICE_TOGGLE: - service_fun = 'toggle' - - if service_fun: - for light in target_lights: - getattr(light, service_fun)(**params) - - for light in target_lights: - if light.should_poll: - light.update_ha_state(True) - return - - # Processing extra data for turn light on request. - profile = profiles.get(params.pop(ATTR_PROFILE, None)) - - if profile: - params.setdefault(ATTR_XY_COLOR, profile[:2]) - params.setdefault(ATTR_BRIGHTNESS, profile[2]) - - color_name = params.pop(ATTR_COLOR_NAME, None) - - if color_name is not None: - params[ATTR_RGB_COLOR] = color_util.color_name_to_rgb(color_name) - - for light in target_lights: - light.turn_on(**params) - - for light in target_lights: - if light.should_poll: - light.update_ha_state(True) - - # Listen for light on and light off service calls. - descriptions = load_yaml_config_file( - os.path.join(os.path.dirname(__file__), 'services.yaml')) - hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_light_service, - descriptions.get(SERVICE_TURN_ON), - schema=LIGHT_TURN_ON_SCHEMA) - - hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service, - descriptions.get(SERVICE_TURN_OFF), - schema=LIGHT_TURN_OFF_SCHEMA) - - hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_light_service, - descriptions.get(SERVICE_TOGGLE), - schema=LIGHT_TOGGLE_SCHEMA) - - return True + return None + return profiles class Light(ToggleEntity): diff --git a/homeassistant/components/light/demo.py b/homeassistant/components/light/demo.py index b6048da243d..614374ce65f 100644 --- a/homeassistant/components/light/demo.py +++ b/homeassistant/components/light/demo.py @@ -129,9 +129,9 @@ class DemoLight(Light): if ATTR_EFFECT in kwargs: self._effect = kwargs[ATTR_EFFECT] - self.update_ha_state() + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the light off.""" self._state = False - self.update_ha_state() + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index 8e0ea5cee83..d9d2407c98c 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -140,7 +140,7 @@ def state(new_state): # Update state. self._is_on = new_state self.group.enqueue(pipeline) - self.update_ha_state() + self.schedule_update_ha_state() return wrapper return decorator diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 424d0a5451c..54fa6b30598 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -283,7 +283,7 @@ class MqttLight(Light): should_update = True if should_update: - self.update_ha_state() + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" @@ -293,4 +293,4 @@ class MqttLight(Light): if self._optimistic: # Optimistically assume that switch has changed state. self._state = False - self.update_ha_state() + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py index 6f1d4e13e7b..d26f5490049 100755 --- a/homeassistant/components/light/mqtt_json.py +++ b/homeassistant/components/light/mqtt_json.py @@ -216,7 +216,7 @@ class MqttJson(Light): should_update = True if should_update: - self.update_ha_state() + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" @@ -231,4 +231,4 @@ class MqttJson(Light): if self._optimistic: # Optimistically assume that the light has changed state. self._state = False - self.update_ha_state() + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/mqtt_template.py b/homeassistant/components/light/mqtt_template.py index f632ba37236..55d4afac231 100755 --- a/homeassistant/components/light/mqtt_template.py +++ b/homeassistant/components/light/mqtt_template.py @@ -263,7 +263,7 @@ class MqttTemplate(Light): ) if self._optimistic: - self.update_ha_state() + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the entity off.""" @@ -283,4 +283,4 @@ class MqttTemplate(Light): ) if self._optimistic: - self.update_ha_state() + self.schedule_update_ha_state() diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index 20da91682ad..86d033cf4ce 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -115,7 +115,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): # optimistically assume that light has changed state self._state = True self._values[set_req.V_LIGHT] = STATE_ON - self.update_ha_state() + self.schedule_update_ha_state() def _turn_on_dimmer(self, **kwargs): """Turn on dimmer child device.""" @@ -135,7 +135,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): # optimistically assume that light has changed state self._brightness = brightness self._values[set_req.V_DIMMER] = percent - self.update_ha_state() + self.schedule_update_ha_state() def _turn_on_rgb_and_w(self, hex_template, **kwargs): """Turn on RGB or RGBW child device.""" @@ -165,7 +165,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): self._white = white if hex_color: self._values[self.value_type] = hex_color - self.update_ha_state() + self.schedule_update_ha_state() def _turn_off_light(self, value_type=None, value=None): """Turn off light child device.""" @@ -211,7 +211,7 @@ class MySensorsLight(mysensors.MySensorsDeviceEntity, Light): self._state = False self._values[value_type] = ( STATE_OFF if set_req.V_LIGHT in self._values else value) - self.update_ha_state() + self.schedule_update_ha_state() def _update_light(self): """Update the controller with values from light child.""" diff --git a/homeassistant/components/light/osramlightify.py b/homeassistant/components/light/osramlightify.py index 55d96236f2d..b4c593d8395 100644 --- a/homeassistant/components/light/osramlightify.py +++ b/homeassistant/components/light/osramlightify.py @@ -189,7 +189,7 @@ class OsramLightifyLight(Light): " %s with transition %s ", self._name, transition) - self.update_ha_state() + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" @@ -209,7 +209,7 @@ class OsramLightifyLight(Light): self._light.set_onoff(0) self._state = self._light.on() - self.update_ha_state() + self.schedule_update_ha_state() def update(self): """Synchronize state with bridge.""" diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py index a33b30736fe..7445977c4f3 100644 --- a/homeassistant/components/light/scsgate.py +++ b/homeassistant/components/light/scsgate.py @@ -84,7 +84,7 @@ class SCSGateLight(Light): ToggleStatusTask(target=self._scs_id, toggled=True)) self._toggled = True - self.update_ha_state() + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" @@ -94,7 +94,7 @@ class SCSGateLight(Light): ToggleStatusTask(target=self._scs_id, toggled=False)) self._toggled = False - self.update_ha_state() + self.schedule_update_ha_state() def process_event(self, message): """Handle a SCSGate message related with this light.""" diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index 59b309e42aa..0508e654f43 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -53,13 +53,13 @@ class VeraLight(VeraDevice, Light): self.vera_device.switch_on() self._state = STATE_ON - self.update_ha_state(True) + self.schedule_update_ha_state(True) def turn_off(self, **kwargs): """Turn the light off.""" self.vera_device.switch_off() self._state = STATE_OFF - self.update_ha_state() + self.schedule_update_ha_state() @property def is_on(self): diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 60e5b4d9ec2..757f144ca57 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -271,8 +271,7 @@ class TestLight(unittest.TestCase): user_file.write('I,WILL,NOT,WORK\n') self.assertFalse(setup_component( - self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}} - )) + self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) def test_light_profiles(self): """Test light profiles."""