Add service to change visibility of a group (#3998)

This commit is contained in:
Pierre Ståhl 2016-10-30 01:54:26 +02:00 committed by Paulus Schoutsen
parent 3f6a5564ad
commit 33e46b484f
7 changed files with 106 additions and 9 deletions

View file

@ -33,6 +33,13 @@ CONF_VIEW = 'view'
ATTR_AUTO = 'auto' ATTR_AUTO = 'auto'
ATTR_ORDER = 'order' ATTR_ORDER = 'order'
ATTR_VIEW = 'view' 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' SERVICE_RELOAD = 'reload'
RELOAD_SERVICE_SCHEMA = vol.Schema({}) RELOAD_SERVICE_SCHEMA = vol.Schema({})
@ -89,6 +96,12 @@ def reload(hass):
hass.services.call(DOMAIN, SERVICE_RELOAD) 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): def expand_entity_ids(hass, entity_ids):
"""Return entity_ids with group entity ids replaced by their members. """Return entity_ids with group entity ids replaced by their members.
@ -164,6 +177,18 @@ def async_setup(hass, config):
return return
hass.loop.create_task(_async_process_config(hass, conf, component)) 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( hass.services.async_register(
DOMAIN, SERVICE_RELOAD, reload_service_handler, DOMAIN, SERVICE_RELOAD, reload_service_handler,
descriptions[DOMAIN][SERVICE_RELOAD], schema=RELOAD_SERVICE_SCHEMA) descriptions[DOMAIN][SERVICE_RELOAD], schema=RELOAD_SERVICE_SCHEMA)
@ -212,6 +237,7 @@ class Group(Entity):
self.group_off = None self.group_off = None
self._assumed_state = False self._assumed_state = False
self._async_unsub_state_changed = None self._async_unsub_state_changed = None
self._visible = True
@staticmethod @staticmethod
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
@ -268,10 +294,20 @@ class Group(Entity):
"""Return the icon of the group.""" """Return the icon of the group."""
return self._icon 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 @property
def hidden(self): def hidden(self):
"""If group should be hidden or not.""" """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 @property
def state_attributes(self): def state_attributes(self):

View file

@ -44,6 +44,18 @@ group:
description: "Reload group configuration." description: "Reload group configuration."
fields: 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: persistent_notification:
create: create:
description: Show a notification in the frontend description: Show a notification in the frontend

View file

@ -86,17 +86,18 @@ class EntityComponent(object):
discovery.async_listen_platform( discovery.async_listen_platform(
self.hass, self.domain, component_platform_discovered) 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. """Extract all known entities from a service call.
Will return all entities if no entities specified in call. Will return all entities if no entities specified in call.
Will return an empty list if entities specified but unknown. Will return an empty list if entities specified but unknown.
""" """
return run_callback_threadsafe( 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() ).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. """Extract all known entities from a service call.
Will return all entities if no entities specified in call. Will return all entities if no entities specified in call.
@ -108,7 +109,7 @@ class EntityComponent(object):
return list(self.entities.values()) return list(self.entities.values())
return [self.entities[entity_id] for entity_id 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] if entity_id in self.entities]
@asyncio.coroutine @asyncio.coroutine

View file

@ -94,7 +94,7 @@ def async_call_from_config(hass, config, blocking=False, variables=None,
domain, service_name, service_data, blocking) 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. """Helper method to extract a list of entity ids from a service call.
Will convert group entity ids to the entity ids it represents. 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 # Entity ID attr can be a list or a string
service_ent_id = service_call.data[ATTR_ENTITY_ID] service_ent_id = service_call.data[ATTR_ENTITY_ID]
if isinstance(service_ent_id, str): if expand_group:
return group.expand_entity_ids(hass, [service_ent_id])
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

View file

@ -352,3 +352,23 @@ class TestComponentsGroup(unittest.TestCase):
assert self.hass.states.entity_ids() == ['group.light'] assert self.hass.states.entity_ids() == ['group.light']
grp.stop() grp.stop()
assert self.hass.states.entity_ids() == [] 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))

View file

@ -7,6 +7,7 @@ from unittest.mock import patch, Mock
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.loader as loader import homeassistant.loader as loader
from homeassistant.components import group
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
@ -205,6 +206,20 @@ class TestHelpersEntityComponent(unittest.TestCase):
assert ['test_domain.test_2'] == \ assert ['test_domain.test_2'] == \
[ent.entity_id for ent in component.extract_from_service(call)] [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): def test_setup_loads_platforms(self):
"""Test the loading of the platforms.""" """Test the loading of the platforms."""
component_setup = Mock(return_value=True) component_setup = Mock(return_value=True)

View file

@ -153,3 +153,6 @@ class TestServiceHelpers(unittest.TestCase):
self.assertEqual(['light.ceiling', 'light.kitchen'], self.assertEqual(['light.ceiling', 'light.kitchen'],
service.extract_entity_ids(self.hass, call)) service.extract_entity_ids(self.hass, call))
self.assertEqual(['group.test'], service.extract_entity_ids(
self.hass, call, expand_group=False))