diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 10e18216ea0..cfc8acb133e 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -20,7 +20,7 @@ import homeassistant.util as util from homeassistant.helpers import extract_entity_ids from homeassistant.loader import get_component from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) + ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE) _LOGGER = logging.getLogger(__name__) @@ -68,6 +68,14 @@ def turn_off(hass, entity_id=None, **service_data): hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data) +def toggle(hass, entity_id=None, **service_data): + """ Toggles specified entity. """ + if entity_id is not None: + service_data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data) + + def setup(hass, config): """ Setup general services related to homeassistant. """ @@ -105,5 +113,6 @@ def setup(hass, config): hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service) hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service) + hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service) return True diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 93321b5fd10..b1017900b17 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -13,7 +13,8 @@ import csv from homeassistant.components import group, discovery, wink, isy994, zwave from homeassistant.config import load_yaml_config_file from homeassistant.const import ( - STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) + STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, + ATTR_ENTITY_ID) from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent import homeassistant.util as util @@ -114,6 +115,18 @@ def turn_off(hass, entity_id=None, transition=None): hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) +def toggle(hass, entity_id=None, transition=None): + """ Toggles all or specified light. """ + data = { + key: value for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_TRANSITION, transition), + ] if value is not None + } + + hass.services.call(DOMAIN, SERVICE_TOGGLE, data) + + # pylint: disable=too-many-branches, too-many-locals, too-many-statements def setup(hass, config): """ Exposes light control via statemachine and services. """ @@ -165,9 +178,15 @@ def setup(hass, config): if transition is not None: params[ATTR_TRANSITION] = transition + 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: - light.turn_off(**params) + getattr(light, service_fun)(**params) for light in target_lights: if light.should_poll: @@ -249,6 +268,9 @@ def setup(hass, config): hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service, descriptions.get(SERVICE_TURN_OFF)) + hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_light_service, + descriptions.get(SERVICE_TOGGLE)) + return True diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index 9908737b7b1..8ad2ea97a6b 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -55,3 +55,15 @@ turn_off: transition: description: Duration in seconds it takes to get to next state example: 60 + +toggle: + description: Toggles a light + + fields: + entity_id: + description: Name(s) of entities to toggle + example: 'light.kitchen' + + transition: + description: Duration in seconds it takes to get to next state + example: 60 diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 1b6b9fbfa44..58256c9b8fd 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -14,10 +14,10 @@ from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.const import ( - STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, + STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE, ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET, - SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_MUTE, SERVICE_TOGGLE, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK) @@ -79,6 +79,7 @@ YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg' SERVICE_TO_METHOD = { SERVICE_TURN_ON: 'turn_on', SERVICE_TURN_OFF: 'turn_off', + SERVICE_TOGGLE: 'toggle', SERVICE_VOLUME_UP: 'volume_up', SERVICE_VOLUME_DOWN: 'volume_down', SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause', @@ -131,6 +132,12 @@ def turn_off(hass, entity_id=None): hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) +def toggle(hass, entity_id=None): + """ Will toggle specified media player or all. """ + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + hass.services.call(DOMAIN, SERVICE_TOGGLE, data) + + def volume_up(hass, entity_id=None): """ Send the media player the command for volume up. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -532,6 +539,13 @@ class MediaPlayerDevice(Entity): """ Boolean if play media command supported. """ return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA) + def toggle(self): + """ Toggles the power on the media player. """ + if self.state in [STATE_OFF, STATE_IDLE]: + self.turn_on() + else: + self.turn_off() + def volume_up(self): """ volume_up media player. """ if self.volume_level < 1: diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index a05a673c3dd..8fb930b6a59 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -15,7 +15,8 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( - STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) + STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, + ATTR_ENTITY_ID) from homeassistant.components import ( group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors) @@ -71,6 +72,12 @@ def turn_off(hass, entity_id=None): hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) +def toggle(hass, entity_id=None): + """ Toggle all or specified switch. """ + data = {ATTR_ENTITY_ID: entity_id} if entity_id else None + hass.services.call(DOMAIN, SERVICE_TOGGLE, data) + + def setup(hass, config): """ Track states and offer events for switches. """ component = EntityComponent( @@ -85,6 +92,8 @@ def setup(hass, config): for switch in target_switches: if service.service == SERVICE_TURN_ON: switch.turn_on() + elif service.service == SERVICE_TOGGLE: + switch.toggle() else: switch.turn_off() @@ -97,6 +106,8 @@ def setup(hass, config): descriptions.get(SERVICE_TURN_OFF)) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service, descriptions.get(SERVICE_TURN_ON)) + hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_switch_service, + descriptions.get(SERVICE_TOGGLE)) return True diff --git a/homeassistant/const.py b/homeassistant/const.py index cb367af362a..02121c986e9 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -126,6 +126,7 @@ SERVICE_HOMEASSISTANT_STOP = "stop" SERVICE_TURN_ON = 'turn_on' SERVICE_TURN_OFF = 'turn_off' +SERVICE_TOGGLE = 'toggle' SERVICE_VOLUME_UP = "volume_up" SERVICE_VOLUME_DOWN = "volume_down" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 980504d1829..86a723f8bd1 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -175,3 +175,10 @@ class ToggleEntity(Entity): def turn_off(self, **kwargs): """ Turn the entity off. """ pass + + def toggle(self, **kwargs): + """ Toggle the entity off. """ + if self.is_on: + self.turn_off(**kwargs) + else: + self.turn_on(**kwargs) diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 2b2830503f4..fcbe89ee27b 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -9,10 +9,9 @@ import unittest import os import homeassistant.loader as loader -import homeassistant.util.color as color_util from homeassistant.const import ( ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, - SERVICE_TURN_ON, SERVICE_TURN_OFF) + SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE) import homeassistant.components.light as light from tests.common import mock_service, get_test_home_assistant @@ -94,6 +93,23 @@ class TestLight(unittest.TestCase): self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) self.assertEqual('transition_val', call.data[light.ATTR_TRANSITION]) + # Test toggle + toggle_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TOGGLE) + + light.toggle( + self.hass, entity_id='entity_id_val', transition='transition_val') + + self.hass.pool.block_till_done() + + self.assertEqual(1, len(toggle_calls)) + call = toggle_calls[-1] + + self.assertEqual(light.DOMAIN, call.domain) + self.assertEqual(SERVICE_TOGGLE, call.service) + self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) + self.assertEqual('transition_val', call.data[light.ATTR_TRANSITION]) + def test_services(self): """ Test the provided services. """ platform = loader.get_component('light.test') @@ -109,7 +125,7 @@ class TestLight(unittest.TestCase): self.assertFalse(light.is_on(self.hass, dev2.entity_id)) self.assertFalse(light.is_on(self.hass, dev3.entity_id)) - # Test basic turn_on, turn_off services + # Test basic turn_on, turn_off, toggle services light.turn_off(self.hass, entity_id=dev1.entity_id) light.turn_on(self.hass, entity_id=dev2.entity_id) @@ -136,6 +152,24 @@ class TestLight(unittest.TestCase): self.assertFalse(light.is_on(self.hass, dev2.entity_id)) self.assertFalse(light.is_on(self.hass, dev3.entity_id)) + # toggle all lights + light.toggle(self.hass) + + self.hass.pool.block_till_done() + + self.assertTrue(light.is_on(self.hass, dev1.entity_id)) + self.assertTrue(light.is_on(self.hass, dev2.entity_id)) + self.assertTrue(light.is_on(self.hass, dev3.entity_id)) + + # toggle all lights + light.toggle(self.hass) + + self.hass.pool.block_till_done() + + self.assertFalse(light.is_on(self.hass, dev1.entity_id)) + self.assertFalse(light.is_on(self.hass, dev2.entity_id)) + self.assertFalse(light.is_on(self.hass, dev3.entity_id)) + # Ensure all attributes process correctly light.turn_on(self.hass, dev1.entity_id, transition=10, brightness=20) diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index 211626ea3fb..a0a7ebc9567 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -12,7 +12,8 @@ from homeassistant.const import ( STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID) + SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_TOGGLE, + ATTR_ENTITY_ID) import homeassistant.components.media_player as media_player from tests.common import mock_service @@ -45,6 +46,7 @@ class TestMediaPlayer(unittest.TestCase): services = { SERVICE_TURN_ON: media_player.turn_on, SERVICE_TURN_OFF: media_player.turn_off, + SERVICE_TOGGLE: media_player.toggle, SERVICE_VOLUME_UP: media_player.volume_up, SERVICE_VOLUME_DOWN: media_player.volume_down, SERVICE_MEDIA_PLAY_PAUSE: media_player.media_play_pause, diff --git a/tests/components/test_init.py b/tests/components/test_init.py index cb170a5c24b..723149b19c6 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -10,7 +10,7 @@ from unittest.mock import patch import homeassistant.core as ha from homeassistant.const import ( - STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF) + STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE) import homeassistant.components as comps from tests.common import get_test_home_assistant @@ -61,6 +61,18 @@ class TestComponentsCore(unittest.TestCase): self.assertEqual(1, len(runs)) + def test_toggle(self): + """ Test toggle method. """ + runs = [] + self.hass.services.register( + 'light', SERVICE_TOGGLE, lambda x: runs.append(1)) + + comps.toggle(self.hass, 'light.Bowl') + + self.hass.pool.block_till_done() + + self.assertEqual(1, len(runs)) + @patch('homeassistant.core.ServiceRegistry.call') def test_turn_on_to_not_block_for_domains_without_service(self, mock_call): self.hass.services.register('light', SERVICE_TURN_ON, lambda x: x)