diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 59683247d5b..ecd79cae3ab 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -33,6 +33,13 @@ CONF_VIEW = 'view' ATTR_AUTO = 'auto' ATTR_ORDER = 'order' ATTR_VIEW = 'view' +ATTR_VISIBLE = 'visible' + +SERVICE_SET_VISIBILITY = 'set_visibility' +SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_VISIBLE): cv.boolean +}) SERVICE_RELOAD = 'reload' RELOAD_SERVICE_SCHEMA = vol.Schema({}) @@ -89,6 +96,12 @@ def reload(hass): hass.services.call(DOMAIN, SERVICE_RELOAD) +def set_visibility(hass, entity_id=None, visible=True): + """Hide or shows a group.""" + data = {ATTR_ENTITY_ID: entity_id, ATTR_VISIBLE: visible} + hass.services.call(DOMAIN, SERVICE_SET_VISIBILITY, data) + + def expand_entity_ids(hass, entity_ids): """Return entity_ids with group entity ids replaced by their members. @@ -164,6 +177,18 @@ def async_setup(hass, config): return hass.loop.create_task(_async_process_config(hass, conf, component)) + @callback + def visibility_service_handler(service): + """Change visibility of a group.""" + visible = service.data.get(ATTR_VISIBLE) + for group in component.async_extract_from_service( + service, expand_group=False): + group.async_set_visible(visible) + + hass.services.async_register( + DOMAIN, SERVICE_SET_VISIBILITY, visibility_service_handler, + descriptions[DOMAIN][SERVICE_SET_VISIBILITY], + schema=SET_VISIBILITY_SERVICE_SCHEMA) hass.services.async_register( DOMAIN, SERVICE_RELOAD, reload_service_handler, descriptions[DOMAIN][SERVICE_RELOAD], schema=RELOAD_SERVICE_SCHEMA) @@ -212,6 +237,7 @@ class Group(Entity): self.group_off = None self._assumed_state = False self._async_unsub_state_changed = None + self._visible = True @staticmethod # pylint: disable=too-many-arguments @@ -268,10 +294,20 @@ class Group(Entity): """Return the icon of the group.""" return self._icon + @callback + def async_set_visible(self, visible): + """Change visibility of the group.""" + if self._visible != visible: + self._visible = visible + self.hass.loop.create_task(self.async_update_ha_state()) + @property def hidden(self): """If group should be hidden or not.""" - return not self._user_defined or self._view + # Visibility from set_visibility service overrides + if self._visible: + return not self._user_defined or self._view + return True @property def state_attributes(self): diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 3df736647cb..fb114f27dd3 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -44,6 +44,18 @@ group: description: "Reload group configuration." fields: + set_visibility: + description: Hide or show a group + + fields: + entity_id: + description: Name(s) of entities to set value + example: 'group.travel' + + visible: + description: True if group should be shown or False if it should be hidden. + example: True + persistent_notification: create: description: Show a notification in the frontend diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index d877f8b0b70..7740f32e4b2 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -86,17 +86,18 @@ class EntityComponent(object): discovery.async_listen_platform( self.hass, self.domain, component_platform_discovered) - def extract_from_service(self, service): + def extract_from_service(self, service, expand_group=True): """Extract all known entities from a service call. Will return all entities if no entities specified in call. Will return an empty list if entities specified but unknown. """ return run_callback_threadsafe( - self.hass.loop, self.async_extract_from_service, service + self.hass.loop, self.async_extract_from_service, service, + expand_group ).result() - def async_extract_from_service(self, service): + def async_extract_from_service(self, service, expand_group=True): """Extract all known entities from a service call. Will return all entities if no entities specified in call. @@ -108,7 +109,7 @@ class EntityComponent(object): return list(self.entities.values()) return [self.entities[entity_id] for entity_id - in extract_entity_ids(self.hass, service) + in extract_entity_ids(self.hass, service, expand_group) if entity_id in self.entities] @asyncio.coroutine diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index ccfeb707fea..21df1244872 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -94,7 +94,7 @@ def async_call_from_config(hass, config, blocking=False, variables=None, domain, service_name, service_data, blocking) -def extract_entity_ids(hass, service_call): +def extract_entity_ids(hass, service_call, expand_group=True): """Helper method to extract a list of entity ids from a service call. Will convert group entity ids to the entity ids it represents. @@ -109,7 +109,17 @@ def extract_entity_ids(hass, service_call): # Entity ID attr can be a list or a string service_ent_id = service_call.data[ATTR_ENTITY_ID] - if isinstance(service_ent_id, str): - return group.expand_entity_ids(hass, [service_ent_id]) + if expand_group: - return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)] + if isinstance(service_ent_id, str): + return group.expand_entity_ids(hass, [service_ent_id]) + + return [ent_id for ent_id in + group.expand_entity_ids(hass, service_ent_id)] + + else: + + if isinstance(service_ent_id, str): + return [service_ent_id] + + return service_ent_id diff --git a/tests/components/test_group.py b/tests/components/test_group.py index 5fe14c6377e..5e8f7f38ae8 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -352,3 +352,23 @@ class TestComponentsGroup(unittest.TestCase): assert self.hass.states.entity_ids() == ['group.light'] grp.stop() assert self.hass.states.entity_ids() == [] + + def test_changing_group_visibility(self): + """Test that a group can be hidden and shown.""" + setup_component(self.hass, 'group', { + 'group': { + 'test_group': 'hello.world,sensor.happy' + } + }) + + group_entity_id = group.ENTITY_ID_FORMAT.format('test_group') + + # Hide the group + group.set_visibility(self.hass, group_entity_id, False) + group_state = self.hass.states.get(group_entity_id) + self.assertTrue(group_state.attributes.get(ATTR_HIDDEN)) + + # Show it again + group.set_visibility(self.hass, group_entity_id, True) + group_state = self.hass.states.get(group_entity_id) + self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN)) diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 3ba9bf3d1ce..47e4e6c7d2f 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -7,6 +7,7 @@ from unittest.mock import patch, Mock import homeassistant.core as ha import homeassistant.loader as loader +from homeassistant.components import group from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers import discovery @@ -205,6 +206,20 @@ class TestHelpersEntityComponent(unittest.TestCase): assert ['test_domain.test_2'] == \ [ent.entity_id for ent in component.extract_from_service(call)] + def test_extract_from_service_no_group_expand(self): + """Test not expanding a group.""" + component = EntityComponent(_LOGGER, DOMAIN, self.hass) + test_group = group.Group.create_group( + self.hass, 'test_group', ['light.Ceiling', 'light.Kitchen']) + component.add_entities([test_group]) + + call = ha.ServiceCall('test', 'service', { + 'entity_id': ['group.test_group'] + }) + + extracted = component.extract_from_service(call, expand_group=False) + self.assertEqual([test_group], extracted) + def test_setup_loads_platforms(self): """Test the loading of the platforms.""" component_setup = Mock(return_value=True) diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index efe21f95d9b..45b9a4919f4 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -153,3 +153,6 @@ class TestServiceHelpers(unittest.TestCase): self.assertEqual(['light.ceiling', 'light.kitchen'], service.extract_entity_ids(self.hass, call)) + + self.assertEqual(['group.test'], service.extract_entity_ids( + self.hass, call, expand_group=False))