diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index ed31e624b913..9da4348362d4 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -9,6 +9,7 @@ from datetime import timedelta import voluptuous as vol +from homeassistant.core import callback import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.helpers.event import track_point_in_time @@ -79,21 +80,22 @@ def setup(hass, config): return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) - def turn_light_on_before_sunset(light_id): + def async_turn_on_before_sunset(light_id): """Helper function to turn on lights. Speed is slow if there are devices home and the light is not on yet. """ if not device_tracker.is_on(hass) or light.is_on(hass, light_id): return - light.turn_on(hass, light_id, - transition=LIGHT_TRANSITION_TIME.seconds, - profile=light_profile) + light.async_turn_on(hass, light_id, + transition=LIGHT_TRANSITION_TIME.seconds, + profile=light_profile) # Track every time sun rises so we can schedule a time-based # pre-sun set event @track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) + @callback def schedule_lights_at_sun_set(hass, entity, old_state, new_state): """The moment sun sets we want to have all the lights on. @@ -104,16 +106,21 @@ def setup(hass, config): if not start_point: return - def turn_on(light_id): + def async_turn_on_factory(light_id): """Lambda can keep track of function parameters. No local parameters. If we put the lambda directly in the below statement only the last light will be turned on. """ - return lambda now: turn_light_on_before_sunset(light_id) + @callback + def async_turn_on_light(now): + """Turn on specific light.""" + async_turn_on_before_sunset(light_id) + + return async_turn_on_light for index, light_id in enumerate(light_ids): - track_point_in_time(hass, turn_on(light_id), + track_point_in_time(hass, async_turn_on_factory(light_id), start_point + index * LIGHT_TRANSITION_TIME) # If the sun is already above horizon schedule the time-based pre-sun set @@ -122,6 +129,7 @@ def setup(hass, config): schedule_lights_at_sun_set(hass, None, None, None) @track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME) + @callback def check_light_on_dev_state_change(hass, entity, old_state, new_state): """Handle tracked device state changes.""" # pylint: disable=unused-variable @@ -136,7 +144,7 @@ def setup(hass, config): # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) - light.turn_on(hass, light_ids, profile=light_profile) + light.async_turn_on(hass, light_ids, profile=light_profile) # Are we in the time span were we would turn on the lights # if someone would be home? @@ -149,7 +157,7 @@ def setup(hass, config): # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: - light.turn_on(hass, light_id) + light.async_turn_on(hass, light_id) else: # If this light didn't happen to be turned on yet so @@ -158,6 +166,7 @@ def setup(hass, config): if not disable_turn_off: @track_state_change(device_group, STATE_HOME, STATE_NOT_HOME) + @callback def turn_off_lights_when_all_leave(hass, entity, old_state, new_state): """Handle device group state change.""" # pylint: disable=unused-variable @@ -166,6 +175,6 @@ def setup(hass, config): logger.info( "Everyone has left but there are lights on. Turning them off") - light.turn_off(hass, light_ids) + light.async_turn_off(hass, light_ids) return True diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index e3437d89e728..b4708164fe2a 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -10,6 +10,7 @@ import csv import voluptuous as vol +from homeassistant.core import callback from homeassistant.components import group from homeassistant.config import load_yaml_config_file from homeassistant.const import ( @@ -20,6 +21,7 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util +from homeassistant.util.async import run_callback_threadsafe DOMAIN = "light" @@ -128,6 +130,18 @@ def turn_on(hass, entity_id=None, transition=None, brightness=None, rgb_color=None, xy_color=None, color_temp=None, white_value=None, profile=None, flash=None, effect=None, color_name=None): """Turn all or specified light on.""" + run_callback_threadsafe( + hass.loop, async_turn_on, hass, entity_id, transition, brightness, + rgb_color, xy_color, color_temp, white_value, + profile, flash, effect, color_name).result() + + +@callback +def async_turn_on(hass, entity_id=None, transition=None, brightness=None, + rgb_color=None, xy_color=None, color_temp=None, + white_value=None, profile=None, flash=None, effect=None, + color_name=None): + """Turn all or specified light on.""" data = { key: value for key, value in [ (ATTR_ENTITY_ID, entity_id), @@ -144,10 +158,17 @@ def turn_on(hass, entity_id=None, transition=None, brightness=None, ] if value is not None } - hass.services.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): + """Turn all or specified light off.""" + run_callback_threadsafe( + hass.loop, async_turn_off, hass, entity_id, transition).result() + + +@callback +def async_turn_off(hass, entity_id=None, transition=None): """Turn all or specified light off.""" data = { key: value for key, value in [ @@ -156,7 +177,8 @@ def turn_off(hass, entity_id=None, transition=None): ] if value is not None } - hass.services.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): diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index 858d49a8e438..20bebe79f9e4 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -49,14 +49,20 @@ def is_on(hass, entity_id=None): def next_setting(hass, entity_id=None): - """Local datetime object of the next sun setting.""" + """Local datetime object of the next sun setting. + + Async friendly. + """ utc_next = next_setting_utc(hass, entity_id) return dt_util.as_local(utc_next) if utc_next else None def next_setting_utc(hass, entity_id=None): - """UTC datetime object of the next sun setting.""" + """UTC datetime object of the next sun setting. + + Async friendly. + """ entity_id = entity_id or ENTITY_ID state = hass.states.get(ENTITY_ID) @@ -71,14 +77,20 @@ def next_setting_utc(hass, entity_id=None): def next_rising(hass, entity_id=None): - """Local datetime object of the next sun rising.""" + """Local datetime object of the next sun rising. + + Async friendly. + """ utc_next = next_rising_utc(hass, entity_id) return dt_util.as_local(utc_next) if utc_next else None def next_rising_utc(hass, entity_id=None): - """UTC datetime object of the next sun rising.""" + """UTC datetime object of the next sun rising. + + Async friendly. + """ entity_id = entity_id or ENTITY_ID state = hass.states.get(ENTITY_ID) diff --git a/tests/common.py b/tests/common.py index fd72a6b635b9..6f017d29b46f 100644 --- a/tests/common.py +++ b/tests/common.py @@ -129,8 +129,12 @@ def mock_service(hass, domain, service): """ calls = [] + @ha.callback + def mock_service(call): + calls.append(call) + # pylint: disable=unnecessary-lambda - hass.services.register(domain, service, lambda call: calls.append(call)) + hass.services.register(domain, service, mock_service) return calls diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 2ab62833edaf..22158402ff91 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -1,6 +1,7 @@ """The tests for the Event automation.""" import unittest +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation @@ -16,6 +17,7 @@ class TestAutomationEvent(unittest.TestCase): self.hass.config.components.append('group') self.calls = [] + @callback def record_call(service): """Helper for recording the call.""" self.calls.append(service) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 2956be98b00e..2459542b629a 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -2,6 +2,7 @@ import unittest from unittest.mock import patch +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation from homeassistant.const import ATTR_ENTITY_ID @@ -22,6 +23,7 @@ class TestAutomation(unittest.TestCase): self.hass.config.components.append('group') self.calls = [] + @callback def record_call(service): """Record call.""" self.calls.append(service) diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index b7da76fda206..4e58dc7a442d 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -1,6 +1,7 @@ """The tests for the MQTT automation.""" import unittest +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation from tests.common import ( @@ -17,6 +18,7 @@ class TestAutomationMQTT(unittest.TestCase): mock_mqtt_component(self.hass) self.calls = [] + @callback def record_call(service): self.calls.append(service) diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index fa2d237ee00c..d0aedd87f466 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -1,6 +1,7 @@ """The tests for numeric state automation.""" import unittest +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation @@ -16,6 +17,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.config.components.append('group') self.calls = [] + @callback def record_call(service): """Helper to record calls.""" self.calls.append(service) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 06c127ca6b72..3b4e44861129 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -3,6 +3,7 @@ import unittest from datetime import timedelta from unittest.mock import patch +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation @@ -21,6 +22,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'hello') self.calls = [] + @callback def record_call(service): self.calls.append(service) diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index ca3d1618013f..475a8f552595 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -3,6 +3,7 @@ from datetime import datetime import unittest from unittest.mock import patch +from homeassistant.core import callback from homeassistant.bootstrap import setup_component from homeassistant.components import sun import homeassistant.components.automation as automation @@ -22,6 +23,7 @@ class TestAutomationSun(unittest.TestCase): self.calls = [] + @callback def record_call(service): self.calls.append(service) diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index fcd1a48983a9..1430d303140f 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -1,6 +1,7 @@ """The tests for the Template automation.""" import unittest +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components.automation as automation @@ -17,6 +18,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'hello') self.calls = [] + @callback def record_call(service): """helper for recording calls.""" self.calls.append(service) diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index dba100aa345f..ff2d20145d94 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -3,6 +3,7 @@ from datetime import timedelta import unittest from unittest.mock import patch +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation @@ -20,6 +21,7 @@ class TestAutomationTime(unittest.TestCase): self.hass.config.components.append('group') self.calls = [] + @callback def record_call(service): self.calls.append(service) diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index e454b8b5b8bf..d81cb8f0bd53 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -1,6 +1,7 @@ """The tests for the location automation.""" import unittest +from homeassistant.core import callback from homeassistant.bootstrap import setup_component from homeassistant.components import automation, zone @@ -25,6 +26,7 @@ class TestAutomationZone(unittest.TestCase): self.calls = [] + @callback def record_call(service): self.calls.append(service) diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 070ca31f8dfc..d11d925ef41d 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -3,7 +3,7 @@ import datetime import unittest from unittest import mock - +from homeassistant.core import callback from homeassistant.bootstrap import setup_component from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, @@ -216,6 +216,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) self.calls = [] + @callback def log_call(call): """Log service calls.""" self.calls.append(call) @@ -306,6 +307,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) self.calls = [] + @callback def log_call(call): """Log service calls.""" self.calls.append(call) @@ -397,6 +399,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) self.calls = [] + @callback def log_call(call): """Log service calls.""" self.calls.append(call) @@ -487,6 +490,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) self.calls = [] + @callback def log_call(call): """Log service calls.""" self.calls.append(call) diff --git a/tests/components/switch/test_template.py b/tests/components/switch/test_template.py index af91c9a565b0..2f67564e6e85 100644 --- a/tests/components/switch/test_template.py +++ b/tests/components/switch/test_template.py @@ -1,7 +1,7 @@ """The tests for the Template switch platform.""" +from homeassistant.core import callback import homeassistant.bootstrap as bootstrap import homeassistant.components as core - from homeassistant.const import ( STATE_ON, STATE_OFF) @@ -21,6 +21,7 @@ class TestTemplateSwitch: self.hass = get_test_home_assistant() self.calls = [] + @callback def record_call(service): """Track function calls..""" self.calls.append(service) diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index 28a808681633..41e7474974d4 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -6,6 +6,7 @@ import unittest import requests +from homeassistant.core import callback from homeassistant import bootstrap, const from homeassistant.components import alexa, http @@ -47,7 +48,11 @@ def setUpModule(): {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, http.CONF_SERVER_PORT: SERVER_PORT}}) - hass.services.register("test", "alexa", lambda call: calls.append(call)) + @callback + def mock_service(call): + calls.append(call) + + hass.services.register("test", "alexa", mock_service) bootstrap.setup_component(hass, alexa.DOMAIN, { # Key is here to verify we allow other keys in config too diff --git a/tests/components/test_api.py b/tests/components/test_api.py index a70048956ebd..28ffa7405e74 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -288,6 +288,7 @@ class TestAPI(unittest.TestCase): """Test if the API allows us to call a service.""" test_value = [] + @ha.callback def listener(service_call): """Helper method that will verify that our service got called.""" test_value.append(1) @@ -307,6 +308,7 @@ class TestAPI(unittest.TestCase): """Test if the API allows us to call a service.""" test_value = [] + @ha.callback def listener(service_call): """Helper method that will verify that our service got called. diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index 1172221f16ff..919c95be4c54 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -3,6 +3,7 @@ import unittest from unittest.mock import patch +from homeassistant.core import callback from homeassistant.bootstrap import setup_component import homeassistant.components as core_components from homeassistant.components import conversation @@ -38,6 +39,7 @@ class TestConversation(unittest.TestCase): """Setup and perform good turn on requests.""" calls = [] + @callback def record_call(service): calls.append(service) @@ -56,6 +58,7 @@ class TestConversation(unittest.TestCase): """Setup and perform good turn off requests.""" calls = [] + @callback def record_call(service): calls.append(service) diff --git a/tests/components/test_script.py b/tests/components/test_script.py index 979e435456ca..76df0a240556 100644 --- a/tests/components/test_script.py +++ b/tests/components/test_script.py @@ -130,6 +130,7 @@ class TestScriptComponent(unittest.TestCase): """Test different ways of passing in variables.""" calls = [] + @callback def record_call(service): """Add recorded event to set.""" calls.append(service) diff --git a/tests/test_core.py b/tests/test_core.py index d3a2d4f353f6..51653500b6e8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -508,7 +508,12 @@ class TestServiceRegistry(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.services = self.hass.services - self.services.register("Test_Domain", "TEST_SERVICE", lambda x: None) + + @ha.callback + def mock_service(call): + pass + + self.services.register("Test_Domain", "TEST_SERVICE", mock_service) # pylint: disable=invalid-name def tearDown(self): @@ -535,6 +540,7 @@ class TestServiceRegistry(unittest.TestCase): """Test call with blocking.""" calls = [] + @ha.callback def service_handler(call): """Service handler.""" calls.append(call) diff --git a/tests/test_remote.py b/tests/test_remote.py index 8692fd4a1332..55d8ca18b5fc 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -116,6 +116,7 @@ class TestRemoteMethods(unittest.TestCase): """Test Python API fire_event.""" test_value = [] + @ha.callback def listener(event): """Helper method that will verify our event got called.""" test_value.append(1) @@ -200,6 +201,7 @@ class TestRemoteMethods(unittest.TestCase): """Test Python API services.call.""" test_value = [] + @ha.callback def listener(service_call): """Helper method that will verify that our service got called.""" test_value.append(1)