Add optional "all" parameter for groups (#17179)
* Added optional mode parameter * Cleanup * Using boolean configuration * Fix invalid syntax * Added tests for all-parameter * Grammar * Lint * Docstrings * Better description
This commit is contained in:
parent
26cf5acd5b
commit
9d56730b8d
3 changed files with 59 additions and 8 deletions
|
@ -30,6 +30,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
CONF_ENTITIES = 'entities'
|
CONF_ENTITIES = 'entities'
|
||||||
CONF_VIEW = 'view'
|
CONF_VIEW = 'view'
|
||||||
CONF_CONTROL = 'control'
|
CONF_CONTROL = 'control'
|
||||||
|
CONF_ALL = 'all'
|
||||||
|
|
||||||
ATTR_ADD_ENTITIES = 'add_entities'
|
ATTR_ADD_ENTITIES = 'add_entities'
|
||||||
ATTR_AUTO = 'auto'
|
ATTR_AUTO = 'auto'
|
||||||
|
@ -39,6 +40,7 @@ ATTR_OBJECT_ID = 'object_id'
|
||||||
ATTR_ORDER = 'order'
|
ATTR_ORDER = 'order'
|
||||||
ATTR_VIEW = 'view'
|
ATTR_VIEW = 'view'
|
||||||
ATTR_VISIBLE = 'visible'
|
ATTR_VISIBLE = 'visible'
|
||||||
|
ATTR_ALL = 'all'
|
||||||
|
|
||||||
SERVICE_SET_VISIBILITY = 'set_visibility'
|
SERVICE_SET_VISIBILITY = 'set_visibility'
|
||||||
SERVICE_SET = 'set'
|
SERVICE_SET = 'set'
|
||||||
|
@ -60,6 +62,7 @@ SET_SERVICE_SCHEMA = vol.Schema({
|
||||||
vol.Optional(ATTR_ICON): cv.string,
|
vol.Optional(ATTR_ICON): cv.string,
|
||||||
vol.Optional(ATTR_CONTROL): CONTROL_TYPES,
|
vol.Optional(ATTR_CONTROL): CONTROL_TYPES,
|
||||||
vol.Optional(ATTR_VISIBLE): cv.boolean,
|
vol.Optional(ATTR_VISIBLE): cv.boolean,
|
||||||
|
vol.Optional(ATTR_ALL): cv.boolean,
|
||||||
vol.Exclusive(ATTR_ENTITIES, 'entities'): cv.entity_ids,
|
vol.Exclusive(ATTR_ENTITIES, 'entities'): cv.entity_ids,
|
||||||
vol.Exclusive(ATTR_ADD_ENTITIES, 'entities'): cv.entity_ids,
|
vol.Exclusive(ATTR_ADD_ENTITIES, 'entities'): cv.entity_ids,
|
||||||
})
|
})
|
||||||
|
@ -85,6 +88,7 @@ GROUP_SCHEMA = vol.Schema({
|
||||||
CONF_NAME: cv.string,
|
CONF_NAME: cv.string,
|
||||||
CONF_ICON: cv.icon,
|
CONF_ICON: cv.icon,
|
||||||
CONF_CONTROL: CONTROL_TYPES,
|
CONF_CONTROL: CONTROL_TYPES,
|
||||||
|
CONF_ALL: cv.boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
@ -223,6 +227,7 @@ async def async_setup(hass, config):
|
||||||
object_id=object_id,
|
object_id=object_id,
|
||||||
entity_ids=entity_ids,
|
entity_ids=entity_ids,
|
||||||
user_defined=False,
|
user_defined=False,
|
||||||
|
mode=service.data.get(ATTR_ALL),
|
||||||
**extra_arg
|
**extra_arg
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
@ -265,6 +270,10 @@ async def async_setup(hass, config):
|
||||||
group.view = service.data[ATTR_VIEW]
|
group.view = service.data[ATTR_VIEW]
|
||||||
need_update = True
|
need_update = True
|
||||||
|
|
||||||
|
if ATTR_ALL in service.data:
|
||||||
|
group.mode = all if service.data[ATTR_ALL] else any
|
||||||
|
need_update = True
|
||||||
|
|
||||||
if need_update:
|
if need_update:
|
||||||
await group.async_update_ha_state()
|
await group.async_update_ha_state()
|
||||||
|
|
||||||
|
@ -310,19 +319,21 @@ async def _async_process_config(hass, config, component):
|
||||||
icon = conf.get(CONF_ICON)
|
icon = conf.get(CONF_ICON)
|
||||||
view = conf.get(CONF_VIEW)
|
view = conf.get(CONF_VIEW)
|
||||||
control = conf.get(CONF_CONTROL)
|
control = conf.get(CONF_CONTROL)
|
||||||
|
mode = conf.get(CONF_ALL)
|
||||||
|
|
||||||
# Don't create tasks and await them all. The order is important as
|
# Don't create tasks and await them all. The order is important as
|
||||||
# groups get a number based on creation order.
|
# groups get a number based on creation order.
|
||||||
await Group.async_create_group(
|
await Group.async_create_group(
|
||||||
hass, name, entity_ids, icon=icon, view=view,
|
hass, name, entity_ids, icon=icon, view=view,
|
||||||
control=control, object_id=object_id)
|
control=control, object_id=object_id, mode=mode)
|
||||||
|
|
||||||
|
|
||||||
class Group(Entity):
|
class Group(Entity):
|
||||||
"""Track a group of entity ids."""
|
"""Track a group of entity ids."""
|
||||||
|
|
||||||
def __init__(self, hass, name, order=None, visible=True, icon=None,
|
def __init__(self, hass, name, order=None, visible=True, icon=None,
|
||||||
view=False, control=None, user_defined=True, entity_ids=None):
|
view=False, control=None, user_defined=True, entity_ids=None,
|
||||||
|
mode=None):
|
||||||
"""Initialize a group.
|
"""Initialize a group.
|
||||||
|
|
||||||
This Object has factory function for creation.
|
This Object has factory function for creation.
|
||||||
|
@ -341,6 +352,9 @@ class Group(Entity):
|
||||||
self.visible = visible
|
self.visible = visible
|
||||||
self.control = control
|
self.control = control
|
||||||
self.user_defined = user_defined
|
self.user_defined = user_defined
|
||||||
|
self.mode = any
|
||||||
|
if mode:
|
||||||
|
self.mode = all
|
||||||
self._order = order
|
self._order = order
|
||||||
self._assumed_state = False
|
self._assumed_state = False
|
||||||
self._async_unsub_state_changed = None
|
self._async_unsub_state_changed = None
|
||||||
|
@ -348,18 +362,19 @@ class Group(Entity):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_group(hass, name, entity_ids=None, user_defined=True,
|
def create_group(hass, name, entity_ids=None, user_defined=True,
|
||||||
visible=True, icon=None, view=False, control=None,
|
visible=True, icon=None, view=False, control=None,
|
||||||
object_id=None):
|
object_id=None, mode=None):
|
||||||
"""Initialize a group."""
|
"""Initialize a group."""
|
||||||
return run_coroutine_threadsafe(
|
return run_coroutine_threadsafe(
|
||||||
Group.async_create_group(
|
Group.async_create_group(
|
||||||
hass, name, entity_ids, user_defined, visible, icon, view,
|
hass, name, entity_ids, user_defined, visible, icon, view,
|
||||||
control, object_id),
|
control, object_id, mode),
|
||||||
hass.loop).result()
|
hass.loop).result()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def async_create_group(hass, name, entity_ids=None,
|
async def async_create_group(hass, name, entity_ids=None,
|
||||||
user_defined=True, visible=True, icon=None,
|
user_defined=True, visible=True, icon=None,
|
||||||
view=False, control=None, object_id=None):
|
view=False, control=None, object_id=None,
|
||||||
|
mode=None):
|
||||||
"""Initialize a group.
|
"""Initialize a group.
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
|
@ -368,7 +383,7 @@ class Group(Entity):
|
||||||
hass, name,
|
hass, name,
|
||||||
order=len(hass.states.async_entity_ids(DOMAIN)),
|
order=len(hass.states.async_entity_ids(DOMAIN)),
|
||||||
visible=visible, icon=icon, view=view, control=control,
|
visible=visible, icon=icon, view=view, control=control,
|
||||||
user_defined=user_defined, entity_ids=entity_ids
|
user_defined=user_defined, entity_ids=entity_ids, mode=mode
|
||||||
)
|
)
|
||||||
|
|
||||||
group.entity_id = async_generate_entity_id(
|
group.entity_id = async_generate_entity_id(
|
||||||
|
@ -557,13 +572,16 @@ class Group(Entity):
|
||||||
if gr_on is None:
|
if gr_on is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# pylint: disable=too-many-boolean-expressions
|
||||||
if tr_state is None or ((gr_state == gr_on and
|
if tr_state is None or ((gr_state == gr_on and
|
||||||
tr_state.state == gr_off) or
|
tr_state.state == gr_off) or
|
||||||
|
(gr_state == gr_off and
|
||||||
|
tr_state.state == gr_on) or
|
||||||
tr_state.state not in (gr_on, gr_off)):
|
tr_state.state not in (gr_on, gr_off)):
|
||||||
if states is None:
|
if states is None:
|
||||||
states = self._tracking_states
|
states = self._tracking_states
|
||||||
|
|
||||||
if any(state.state == gr_on for state in states):
|
if self.mode(state.state == gr_on for state in states):
|
||||||
self._state = gr_on
|
self._state = gr_on
|
||||||
else:
|
else:
|
||||||
self._state = gr_off
|
self._state = gr_off
|
||||||
|
@ -576,7 +594,7 @@ class Group(Entity):
|
||||||
if states is None:
|
if states is None:
|
||||||
states = self._tracking_states
|
states = self._tracking_states
|
||||||
|
|
||||||
self._assumed_state = any(
|
self._assumed_state = self.mode(
|
||||||
state.attributes.get(ATTR_ASSUMED_STATE) for state
|
state.attributes.get(ATTR_ASSUMED_STATE) for state
|
||||||
in states)
|
in states)
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,9 @@ set:
|
||||||
add_entities:
|
add_entities:
|
||||||
description: List of members they will change on group listening.
|
description: List of members they will change on group listening.
|
||||||
example: domain.entity_id1, domain.entity_id2
|
example: domain.entity_id1, domain.entity_id2
|
||||||
|
all:
|
||||||
|
description: Enable this option if the group should only turn on when all entities are on.
|
||||||
|
example: True
|
||||||
|
|
||||||
remove:
|
remove:
|
||||||
description: Remove a user group.
|
description: Remove a user group.
|
||||||
|
|
|
@ -108,6 +108,36 @@ class TestComponentsGroup(unittest.TestCase):
|
||||||
group_state = self.hass.states.get(test_group.entity_id)
|
group_state = self.hass.states.get(test_group.entity_id)
|
||||||
self.assertEqual(STATE_ON, group_state.state)
|
self.assertEqual(STATE_ON, group_state.state)
|
||||||
|
|
||||||
|
def test_allgroup_stays_off_if_all_are_off_and_one_turns_on(self):
|
||||||
|
"""Group with all: true, stay off if one device turns on."""
|
||||||
|
self.hass.states.set('light.Bowl', STATE_OFF)
|
||||||
|
self.hass.states.set('light.Ceiling', STATE_OFF)
|
||||||
|
test_group = group.Group.create_group(
|
||||||
|
self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False,
|
||||||
|
mode=True)
|
||||||
|
|
||||||
|
# Turn one on
|
||||||
|
self.hass.states.set('light.Ceiling', STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
group_state = self.hass.states.get(test_group.entity_id)
|
||||||
|
self.assertEqual(STATE_OFF, group_state.state)
|
||||||
|
|
||||||
|
def test_allgroup_turn_on_if_last_turns_on(self):
|
||||||
|
"""Group with all: true, turn on if all devices are on."""
|
||||||
|
self.hass.states.set('light.Bowl', STATE_ON)
|
||||||
|
self.hass.states.set('light.Ceiling', STATE_OFF)
|
||||||
|
test_group = group.Group.create_group(
|
||||||
|
self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False,
|
||||||
|
mode=True)
|
||||||
|
|
||||||
|
# Turn one on
|
||||||
|
self.hass.states.set('light.Ceiling', STATE_ON)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
group_state = self.hass.states.get(test_group.entity_id)
|
||||||
|
self.assertEqual(STATE_ON, group_state.state)
|
||||||
|
|
||||||
def test_is_on(self):
|
def test_is_on(self):
|
||||||
"""Test is_on method."""
|
"""Test is_on method."""
|
||||||
self.hass.states.set('light.Bowl', STATE_ON)
|
self.hass.states.set('light.Bowl', STATE_ON)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue