diff --git a/config/custom_components/example.py b/config/custom_components/example.py index f7ece4db5ea..51565d00d89 100644 --- a/config/custom_components/example.py +++ b/config/custom_components/example.py @@ -25,7 +25,7 @@ def setup(hass, config): hass.track_time_change(print, second=[0, 30]) # See also (defined in homeassistant/__init__.py): - # hass.track_state_change + # hass.states.track_change # hass.track_point_in_time # Tells the bootstrapper that the component was succesfully initialized diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index c9e8013b4eb..3ee3e0d4539 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -27,7 +27,7 @@ EVENT_HOMEASSISTANT_START = "homeassistant_start" EVENT_HOMEASSISTANT_STOP = "homeassistant_stop" EVENT_STATE_CHANGED = "state_changed" EVENT_TIME_CHANGED = "time_changed" -EVENT_CALL_SERVICE = "call_service" +EVENT_CALL_SERVICE = "services.call" ATTR_NOW = "now" ATTR_DOMAIN = "domain" @@ -92,42 +92,6 @@ class HomeAssistant(object): self.stop() - def call_service(self, domain, service, service_data=None): - """ Fires event to call specified service. """ - event_data = service_data or {} - event_data[ATTR_DOMAIN] = domain - event_data[ATTR_SERVICE] = service - - self.bus.fire(EVENT_CALL_SERVICE, event_data) - - def track_state_change(self, entity_ids, action, - from_state=None, to_state=None): - """ - Track specific state changes. - entity_ids, from_state and to_state can be string or list. - Use list to match multiple. - """ - from_state = _process_match_param(from_state) - to_state = _process_match_param(to_state) - - # Ensure it is a list with entity ids we want to match on - if isinstance(entity_ids, str): - entity_ids = [entity_ids] - - @ft.wraps(action) - def state_listener(event): - """ The listener that listens for specific state changes. """ - if event.data['entity_id'] in entity_ids and \ - 'old_state' in event.data and \ - _matcher(event.data['old_state'].state, from_state) and \ - _matcher(event.data['new_state'].state, to_state): - - action(event.data['entity_id'], - event.data['old_state'], - event.data['new_state']) - - self.bus.listen(EVENT_STATE_CHANGED, state_listener) - def track_point_in_time(self, action, point_in_time): """ Adds a listener that fires once at or after a spefic point in time. @@ -231,6 +195,33 @@ class HomeAssistant(object): self.bus.listen_once(event_type, listener) + def track_state_change(self, entity_ids, action, + from_state=None, to_state=None): + """ + Track specific state changes. + entity_ids, from_state and to_state can be string or list. + Use list to match multiple. + + THIS METHOD IS DEPRECATED. Use hass.states.track_change + """ + _LOGGER.warning(( + "hass.track_state_change is deprecated. " + "Use hass.states.track_change")) + + self.states.track_change(entity_ids, action, from_state, to_state) + + def call_service(self, domain, service, service_data=None): + """ + Fires event to call specified service. + + THIS METHOD IS DEPRECATED. Use hass.services.call + """ + _LOGGER.warning(( + "hass.services.call is deprecated. " + "Use hass.services.call")) + + self.services.call(domain, service, service_data) + def _process_match_param(parameter): """ Wraps parameter in a list if it is not one and returns it. """ @@ -561,6 +552,33 @@ class StateMachine(object): self._bus.fire(EVENT_STATE_CHANGED, event_data) + def track_change(self, entity_ids, action, from_state=None, to_state=None): + """ + Track specific state changes. + entity_ids, from_state and to_state can be string or list. + Use list to match multiple. + """ + from_state = _process_match_param(from_state) + to_state = _process_match_param(to_state) + + # Ensure it is a list with entity ids we want to match on + if isinstance(entity_ids, str): + entity_ids = [entity_ids] + + @ft.wraps(action) + def state_listener(event): + """ The listener that listens for specific state changes. """ + if event.data['entity_id'] in entity_ids and \ + 'old_state' in event.data and \ + _matcher(event.data['old_state'].state, from_state) and \ + _matcher(event.data['new_state'].state, to_state): + + action(event.data['entity_id'], + event.data['old_state'], + event.data['new_state']) + + self._bus.listen(EVENT_STATE_CHANGED, state_listener) + # pylint: disable=too-few-public-methods class ServiceCall(object): @@ -588,6 +606,7 @@ class ServiceRegistry(object): self._services = {} self._lock = threading.Lock() self._pool = pool or create_worker_pool() + self._bus = bus bus.listen(EVENT_CALL_SERVICE, self._event_to_service_call) @property @@ -609,6 +628,23 @@ class ServiceRegistry(object): else: self._services[domain] = {service: service_func} + def call(self, domain, service, service_data=None): + """ + Fires event to call specified service. + + This method will fire an event to call the service. + This event will be picked up by this ServiceRegistry and any + other ServiceRegistry that is listening on the EventBus. + + Because the service is sent as an event you are not allowed to use + the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data. + """ + event_data = service_data or {} + event_data[ATTR_DOMAIN] = domain + event_data[ATTR_SERVICE] = service + + self._bus.fire(EVENT_CALL_SERVICE, event_data) + def _event_to_service_call(self, event): """ Calls a service from an event. """ service_data = dict(event.data) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 9b04d266668..3acaf11a4fd 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -85,7 +85,7 @@ def turn_on(hass, entity_id=None, **service_data): if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id - hass.call_service(ha.DOMAIN, SERVICE_TURN_ON, service_data) + hass.services.call(ha.DOMAIN, SERVICE_TURN_ON, service_data) def turn_off(hass, entity_id=None, **service_data): @@ -93,7 +93,7 @@ def turn_off(hass, entity_id=None, **service_data): if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id - hass.call_service(ha.DOMAIN, SERVICE_TURN_OFF, service_data) + hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data) def extract_entity_ids(hass, service): @@ -195,7 +195,7 @@ def setup(hass, config): # ent_ids is a generator, convert it to a list. data[ATTR_ENTITY_ID] = list(ent_ids) - hass.call_service(domain, service.service, data) + hass.services.call(domain, service.service, data) hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service) hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service) diff --git a/homeassistant/components/chromecast.py b/homeassistant/components/chromecast.py index c7baee64496..d6e9971dc0d 100644 --- a/homeassistant/components/chromecast.py +++ b/homeassistant/components/chromecast.py @@ -48,56 +48,56 @@ def turn_off(hass, entity_id=None): """ Will turn off specified Chromecast or all. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_TURN_OFF, data) + hass.services.call(DOMAIN, components.SERVICE_TURN_OFF, data) def volume_up(hass, entity_id=None): """ Send the chromecast the command for volume up. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_VOLUME_UP, data) + hass.services.call(DOMAIN, components.SERVICE_VOLUME_UP, data) def volume_down(hass, entity_id=None): """ Send the chromecast the command for volume down. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_VOLUME_DOWN, data) + hass.services.call(DOMAIN, components.SERVICE_VOLUME_DOWN, data) def media_play_pause(hass, entity_id=None): """ Send the chromecast the command for play/pause. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE, data) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE, data) def media_play(hass, entity_id=None): """ Send the chromecast the command for play/pause. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_MEDIA_PLAY, data) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_PLAY, data) def media_pause(hass, entity_id=None): """ Send the chromecast the command for play/pause. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_MEDIA_PAUSE, data) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_PAUSE, data) def media_next_track(hass, entity_id=None): """ Send the chromecast the command for next track. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK, data) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK, data) def media_prev_track(hass, entity_id=None): """ Send the chromecast the command for prev track. """ data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.call_service(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK, data) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK, data) # pylint: disable=too-many-locals, too-many-branches diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 7bb1f9cbddc..5e2b99e64a7 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -92,8 +92,8 @@ def setup(hass, config): # Track every time sun rises so we can schedule a time-based # pre-sun set event - hass.track_state_change(sun.ENTITY_ID, schedule_light_on_sun_rise, - sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) + hass.states.track_change(sun.ENTITY_ID, schedule_light_on_sun_rise, + sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) # If the sun is already above horizon # schedule the time-based pre-sun set event @@ -152,12 +152,14 @@ def setup(hass, config): light.turn_off(hass) # Track home coming of each device - hass.track_state_change(device_entity_ids, check_light_on_dev_state_change, - components.STATE_NOT_HOME, components.STATE_HOME) + hass.states.track_change( + device_entity_ids, check_light_on_dev_state_change, + components.STATE_NOT_HOME, components.STATE_HOME) # Track when all devices are gone to shut down lights - hass.track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES, - check_light_on_dev_state_change, - components.STATE_HOME, components.STATE_NOT_HOME) + hass.states.track_change( + device_tracker.ENTITY_ID_ALL_DEVICES, + check_light_on_dev_state_change, + components.STATE_HOME, components.STATE_NOT_HOME) return True diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 8c0ff0b763a..5376b65131a 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -186,7 +186,7 @@ def setup_group(hass, name, entity_ids, user_defined=True): if entity_id != ent_id]): hass.states.set(group_entity_id, group_off, state_attr) - hass.track_state_change(entity_ids, update_group_state) + hass.states.track_change(entity_ids, update_group_state) hass.states.set(group_entity_id, group_state, state_attr) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 0d115d95524..02a1bea0272 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -481,7 +481,7 @@ class RequestHandler(SimpleHTTPRequestHandler): domain = path_match.group('domain') service = path_match.group('service') - self.server.hass.call_service(domain, service, data) + self.server.hass.services.call(domain, service, data) self._json_message("Service {}/{} called.".format(domain, service)) diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index 40d4b938e47..e2d156d5c7e 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -14,32 +14,32 @@ DEPENDENCIES = [] def volume_up(hass): """ Press the keyboard button for volume up. """ - hass.call_service(DOMAIN, components.SERVICE_VOLUME_UP) + hass.services.call(DOMAIN, components.SERVICE_VOLUME_UP) def volume_down(hass): """ Press the keyboard button for volume down. """ - hass.call_service(DOMAIN, components.SERVICE_VOLUME_DOWN) + hass.services.call(DOMAIN, components.SERVICE_VOLUME_DOWN) def volume_mute(hass): """ Press the keyboard button for muting volume. """ - hass.call_service(DOMAIN, components.SERVICE_VOLUME_MUTE) + hass.services.call(DOMAIN, components.SERVICE_VOLUME_MUTE) def media_play_pause(hass): """ Press the keyboard button for play/pause. """ - hass.call_service(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE) def media_next_track(hass): """ Press the keyboard button for next track. """ - hass.call_service(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK) def media_prev_track(hass): """ Press the keyboard button for prev track. """ - hass.call_service(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK) + hass.services.call(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK) # pylint: disable=unused-argument diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index b7e6a4fd348..957a318ce3c 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -118,7 +118,7 @@ def turn_on(hass, entity_id=None, transition=None, brightness=None, if xy_color: data[ATTR_XY_COLOR] = xy_color - hass.call_service(DOMAIN, SERVICE_TURN_ON, data) + hass.services.call(DOMAIN, SERVICE_TURN_ON, data) def turn_off(hass, entity_id=None, transition=None): @@ -131,7 +131,7 @@ def turn_off(hass, entity_id=None, transition=None): if transition is not None: data[ATTR_TRANSITION] = transition - hass.call_service(DOMAIN, SERVICE_TURN_OFF, data) + hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) # pylint: disable=too-many-branches, too-many-locals diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 96325b92fac..87e50eaa41c 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -43,14 +43,14 @@ def turn_on(hass, entity_id=None): """ Turns all or specified switch on. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None - hass.call_service(DOMAIN, SERVICE_TURN_ON, data) + hass.services.call(DOMAIN, SERVICE_TURN_ON, data) def turn_off(hass, entity_id=None): """ Turns all or specified switch off. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None - hass.call_service(DOMAIN, SERVICE_TURN_OFF, data) + hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) # pylint: disable=too-many-branches diff --git a/test/test_component_demo.py b/test/test_component_demo.py index 54619266078..dd3e47cb97e 100644 --- a/test/test_component_demo.py +++ b/test/test_component_demo.py @@ -32,14 +32,14 @@ class TestDemo(unittest.TestCase): # Focus on 1 entity entity_id = self.hass.states.entity_ids(domain)[0] - self.hass.call_service( + self.hass.services.call( domain, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}) self.hass._pool.block_till_done() self.assertEqual(STATE_ON, self.hass.states.get(entity_id).state) - self.hass.call_service( + self.hass.services.call( domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}) self.hass._pool.block_till_done() @@ -47,7 +47,7 @@ class TestDemo(unittest.TestCase): self.assertEqual(STATE_OFF, self.hass.states.get(entity_id).state) # Act on all - self.hass.call_service(domain, SERVICE_TURN_ON) + self.hass.services.call(domain, SERVICE_TURN_ON) self.hass._pool.block_till_done() @@ -55,7 +55,7 @@ class TestDemo(unittest.TestCase): self.assertEqual( STATE_ON, self.hass.states.get(entity_id).state) - self.hass.call_service(domain, SERVICE_TURN_OFF) + self.hass.services.call(domain, SERVICE_TURN_OFF) self.hass._pool.block_till_done() diff --git a/test/test_core.py b/test/test_core.py index 0c046ed0cc2..012aef2a1f3 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -52,7 +52,7 @@ class TestHomeAssistant(unittest.TestCase): self.assertTrue(blocking_thread.is_alive()) - self.hass.call_service(ha.DOMAIN, ha.SERVICE_HOMEASSISTANT_STOP) + self.hass.services.call(ha.DOMAIN, ha.SERVICE_HOMEASSISTANT_STOP) self.hass._pool.block_till_done() # hass.block_till_stopped checks every second if it should quit @@ -64,43 +64,6 @@ class TestHomeAssistant(unittest.TestCase): self.assertFalse(blocking_thread.is_alive()) - def test_track_state_change(self): - """ Test track_state_change. """ - # 2 lists to track how often our callbacks got called - specific_runs = [] - wildcard_runs = [] - - self.hass.track_state_change( - 'light.Bowl', lambda a, b, c: specific_runs.append(1), 'on', 'off') - - self.hass.track_state_change( - 'light.Bowl', lambda a, b, c: wildcard_runs.append(1), - ha.MATCH_ALL, ha.MATCH_ALL) - - # Set same state should not trigger a state change/listener - self.hass.states.set('light.Bowl', 'on') - self.hass._pool.block_till_done() - self.assertEqual(0, len(specific_runs)) - self.assertEqual(0, len(wildcard_runs)) - - # State change off -> on - self.hass.states.set('light.Bowl', 'off') - self.hass._pool.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) - - # State change off -> off - self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) - self.hass._pool.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(2, len(wildcard_runs)) - - # State change off -> on - self.hass.states.set('light.Bowl', 'on') - self.hass._pool.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(3, len(wildcard_runs)) - def test_track_point_in_time(self): """ Test track point in time. """ before_birthday = datetime(1985, 7, 9, 12, 0, 0) @@ -285,6 +248,43 @@ class TestStateMachine(unittest.TestCase): # If it does not exist, we should get False self.assertFalse(self.states.remove('light.Bowl')) + def test_track_change(self): + """ Test states.track_change. """ + # 2 lists to track how often our callbacks got called + specific_runs = [] + wildcard_runs = [] + + self.states.track_change( + 'light.Bowl', lambda a, b, c: specific_runs.append(1), 'on', 'off') + + self.states.track_change( + 'light.Bowl', lambda a, b, c: wildcard_runs.append(1), + ha.MATCH_ALL, ha.MATCH_ALL) + + # Set same state should not trigger a state change/listener + self.states.set('light.Bowl', 'on') + self.bus._pool.block_till_done() + self.assertEqual(0, len(specific_runs)) + self.assertEqual(0, len(wildcard_runs)) + + # State change off -> on + self.states.set('light.Bowl', 'off') + self.bus._pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(1, len(wildcard_runs)) + + # State change off -> off + self.states.set('light.Bowl', 'off', {"some_attr": 1}) + self.bus._pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(2, len(wildcard_runs)) + + # State change off -> on + self.states.set('light.Bowl', 'on') + self.bus._pool.block_till_done() + self.assertEqual(1, len(specific_runs)) + self.assertEqual(3, len(wildcard_runs)) + class TestServiceCall(unittest.TestCase): """ Test ServiceCall class. """ diff --git a/test/test_remote.py b/test/test_remote.py index adeeaaa8e72..1b093b1c56a 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -154,7 +154,7 @@ class TestRemoteMethods(unittest.TestCase): self.assertEqual({}, remote.get_services(broken_api)) def test_call_service(self): - """ Test Python API call_service. """ + """ Test Python API services.call. """ test_value = [] def listener(service_call): # pylint: disable=unused-argument