Add service to change visibility of a group (#3998)
This commit is contained in:
parent
3f6a5564ad
commit
33e46b484f
7 changed files with 106 additions and 9 deletions
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue