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_VIEW = 'view'
|
||||
CONF_CONTROL = 'control'
|
||||
CONF_ALL = 'all'
|
||||
|
||||
ATTR_ADD_ENTITIES = 'add_entities'
|
||||
ATTR_AUTO = 'auto'
|
||||
|
@ -39,6 +40,7 @@ ATTR_OBJECT_ID = 'object_id'
|
|||
ATTR_ORDER = 'order'
|
||||
ATTR_VIEW = 'view'
|
||||
ATTR_VISIBLE = 'visible'
|
||||
ATTR_ALL = 'all'
|
||||
|
||||
SERVICE_SET_VISIBILITY = 'set_visibility'
|
||||
SERVICE_SET = 'set'
|
||||
|
@ -60,6 +62,7 @@ SET_SERVICE_SCHEMA = vol.Schema({
|
|||
vol.Optional(ATTR_ICON): cv.string,
|
||||
vol.Optional(ATTR_CONTROL): CONTROL_TYPES,
|
||||
vol.Optional(ATTR_VISIBLE): cv.boolean,
|
||||
vol.Optional(ATTR_ALL): cv.boolean,
|
||||
vol.Exclusive(ATTR_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_ICON: cv.icon,
|
||||
CONF_CONTROL: CONTROL_TYPES,
|
||||
CONF_ALL: cv.boolean,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
|
@ -223,6 +227,7 @@ async def async_setup(hass, config):
|
|||
object_id=object_id,
|
||||
entity_ids=entity_ids,
|
||||
user_defined=False,
|
||||
mode=service.data.get(ATTR_ALL),
|
||||
**extra_arg
|
||||
)
|
||||
return
|
||||
|
@ -265,6 +270,10 @@ async def async_setup(hass, config):
|
|||
group.view = service.data[ATTR_VIEW]
|
||||
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:
|
||||
await group.async_update_ha_state()
|
||||
|
||||
|
@ -310,19 +319,21 @@ async def _async_process_config(hass, config, component):
|
|||
icon = conf.get(CONF_ICON)
|
||||
view = conf.get(CONF_VIEW)
|
||||
control = conf.get(CONF_CONTROL)
|
||||
mode = conf.get(CONF_ALL)
|
||||
|
||||
# Don't create tasks and await them all. The order is important as
|
||||
# groups get a number based on creation order.
|
||||
await Group.async_create_group(
|
||||
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):
|
||||
"""Track a group of entity ids."""
|
||||
|
||||
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.
|
||||
|
||||
This Object has factory function for creation.
|
||||
|
@ -341,6 +352,9 @@ class Group(Entity):
|
|||
self.visible = visible
|
||||
self.control = control
|
||||
self.user_defined = user_defined
|
||||
self.mode = any
|
||||
if mode:
|
||||
self.mode = all
|
||||
self._order = order
|
||||
self._assumed_state = False
|
||||
self._async_unsub_state_changed = None
|
||||
|
@ -348,18 +362,19 @@ class Group(Entity):
|
|||
@staticmethod
|
||||
def create_group(hass, name, entity_ids=None, user_defined=True,
|
||||
visible=True, icon=None, view=False, control=None,
|
||||
object_id=None):
|
||||
object_id=None, mode=None):
|
||||
"""Initialize a group."""
|
||||
return run_coroutine_threadsafe(
|
||||
Group.async_create_group(
|
||||
hass, name, entity_ids, user_defined, visible, icon, view,
|
||||
control, object_id),
|
||||
control, object_id, mode),
|
||||
hass.loop).result()
|
||||
|
||||
@staticmethod
|
||||
async def async_create_group(hass, name, entity_ids=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.
|
||||
|
||||
This method must be run in the event loop.
|
||||
|
@ -368,7 +383,7 @@ class Group(Entity):
|
|||
hass, name,
|
||||
order=len(hass.states.async_entity_ids(DOMAIN)),
|
||||
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(
|
||||
|
@ -557,13 +572,16 @@ class Group(Entity):
|
|||
if gr_on is None:
|
||||
return
|
||||
|
||||
# pylint: disable=too-many-boolean-expressions
|
||||
if tr_state is None or ((gr_state == gr_on and
|
||||
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)):
|
||||
if states is None:
|
||||
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
|
||||
else:
|
||||
self._state = gr_off
|
||||
|
@ -576,7 +594,7 @@ class Group(Entity):
|
|||
if states is None:
|
||||
states = self._tracking_states
|
||||
|
||||
self._assumed_state = any(
|
||||
self._assumed_state = self.mode(
|
||||
state.attributes.get(ATTR_ASSUMED_STATE) for state
|
||||
in states)
|
||||
|
||||
|
|
|
@ -40,6 +40,9 @@ set:
|
|||
add_entities:
|
||||
description: List of members they will change on group listening.
|
||||
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:
|
||||
description: Remove a user group.
|
||||
|
|
|
@ -108,6 +108,36 @@ class TestComponentsGroup(unittest.TestCase):
|
|||
group_state = self.hass.states.get(test_group.entity_id)
|
||||
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):
|
||||
"""Test is_on method."""
|
||||
self.hass.states.set('light.Bowl', STATE_ON)
|
||||
|
|
Loading…
Add table
Reference in a new issue