Group component is more flexible when to setup a group
This commit is contained in:
parent
ea1e4108cc
commit
df3521e706
2 changed files with 82 additions and 76 deletions
|
@ -40,8 +40,41 @@ class TestComponentsGroup(unittest.TestCase):
|
||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_setup_and_monitor_group(self):
|
def test_setup_group(self):
|
||||||
""" Test setup_group method. """
|
""" Test setup_group method. """
|
||||||
|
# Try to setup a group with mixed groupable states
|
||||||
|
self.hass.states.set('device_tracker.Paulus', STATE_HOME)
|
||||||
|
self.assertTrue(group.setup_group(
|
||||||
|
self.hass, 'person_and_light',
|
||||||
|
['light.Bowl', 'device_tracker.Paulus']))
|
||||||
|
self.assertEqual(
|
||||||
|
STATE_ON,
|
||||||
|
self.hass.states.get(
|
||||||
|
group.ENTITY_ID_FORMAT.format('person_and_light')).state)
|
||||||
|
|
||||||
|
# Try to setup a group with a non existing state
|
||||||
|
self.assertNotIn('non.existing', self.hass.states.entity_ids())
|
||||||
|
self.assertTrue(group.setup_group(
|
||||||
|
self.hass, 'light_and_nothing',
|
||||||
|
['light.Bowl', 'non.existing']))
|
||||||
|
self.assertEqual(
|
||||||
|
STATE_ON,
|
||||||
|
self.hass.states.get(
|
||||||
|
group.ENTITY_ID_FORMAT.format('light_and_nothing')).state)
|
||||||
|
|
||||||
|
# Try to setup a group with non groupable states
|
||||||
|
self.hass.states.set('cast.living_room', "Plex")
|
||||||
|
self.hass.states.set('cast.bedroom', "Netflix")
|
||||||
|
self.assertFalse(
|
||||||
|
group.setup_group(
|
||||||
|
self.hass, 'chromecasts',
|
||||||
|
['cast.living_room', 'cast.bedroom']))
|
||||||
|
|
||||||
|
# Try to setup an empty group
|
||||||
|
self.assertFalse(group.setup_group(self.hass, 'nothing', []))
|
||||||
|
|
||||||
|
def test_monitor_group(self):
|
||||||
|
""" Test if the group keeps track of states. """
|
||||||
|
|
||||||
# Test if group setup in our init mode is ok
|
# Test if group setup in our init mode is ok
|
||||||
self.assertIn(self.group_name, self.hass.states.entity_ids())
|
self.assertIn(self.group_name, self.hass.states.entity_ids())
|
||||||
|
@ -66,41 +99,6 @@ class TestComponentsGroup(unittest.TestCase):
|
||||||
group_state = self.hass.states.get(self.group_name)
|
group_state = self.hass.states.get(self.group_name)
|
||||||
self.assertEqual(STATE_ON, group_state.state)
|
self.assertEqual(STATE_ON, group_state.state)
|
||||||
|
|
||||||
# Try to setup a group with mixed groupable states
|
|
||||||
self.hass.states.set('device_tracker.Paulus', STATE_HOME)
|
|
||||||
self.assertFalse(group.setup_group(
|
|
||||||
self.hass, 'person_and_light',
|
|
||||||
['light.Bowl', 'device_tracker.Paulus']))
|
|
||||||
|
|
||||||
# Try to setup a group with a non existing state
|
|
||||||
self.assertNotIn('non.existing', self.hass.states.entity_ids())
|
|
||||||
self.assertFalse(group.setup_group(
|
|
||||||
self.hass, 'light_and_nothing',
|
|
||||||
['light.Bowl', 'non.existing']))
|
|
||||||
|
|
||||||
# Try to setup a group with non groupable states
|
|
||||||
self.hass.states.set('cast.living_room', "Plex")
|
|
||||||
self.hass.states.set('cast.bedroom', "Netflix")
|
|
||||||
self.assertFalse(
|
|
||||||
group.setup_group(
|
|
||||||
self.hass, 'chromecasts',
|
|
||||||
['cast.living_room', 'cast.bedroom']))
|
|
||||||
|
|
||||||
# Try to setup an empty group
|
|
||||||
self.assertFalse(group.setup_group(self.hass, 'nothing', []))
|
|
||||||
|
|
||||||
def test__get_group_type(self):
|
|
||||||
""" Test _get_group_type method. """
|
|
||||||
self.assertEqual('on_off', group._get_group_type(STATE_ON))
|
|
||||||
self.assertEqual('on_off', group._get_group_type(STATE_OFF))
|
|
||||||
self.assertEqual('home_not_home',
|
|
||||||
group._get_group_type(STATE_HOME))
|
|
||||||
self.assertEqual('home_not_home',
|
|
||||||
group._get_group_type(STATE_NOT_HOME))
|
|
||||||
|
|
||||||
# Unsupported state
|
|
||||||
self.assertIsNone(group._get_group_type('unsupported_state'))
|
|
||||||
|
|
||||||
def test_is_on(self):
|
def test_is_on(self):
|
||||||
""" Test is_on method. """
|
""" Test is_on method. """
|
||||||
self.assertTrue(group.is_on(self.hass, self.group_name))
|
self.assertTrue(group.is_on(self.hass, self.group_name))
|
||||||
|
|
|
@ -19,21 +19,19 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||||
|
|
||||||
ATTR_AUTO = "auto"
|
ATTR_AUTO = "auto"
|
||||||
|
|
||||||
_GROUP_TYPES = {
|
# List of ON/OFF state tuples for groupable states
|
||||||
"on_off": (STATE_ON, STATE_OFF),
|
_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME)]
|
||||||
"home_not_home": (STATE_HOME, STATE_NOT_HOME)
|
|
||||||
}
|
|
||||||
|
|
||||||
_GROUPS = {}
|
_GROUPS = {}
|
||||||
|
|
||||||
|
|
||||||
def _get_group_type(state):
|
def _get_group_on_off(state):
|
||||||
""" Determine the group type based on the given group type. """
|
""" Determine the group on/off states based on a state. """
|
||||||
for group_type, states in _GROUP_TYPES.items():
|
for states in _GROUP_TYPES:
|
||||||
if state in states:
|
if state in states:
|
||||||
return group_type
|
return states
|
||||||
|
|
||||||
return None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def is_on(hass, entity_id):
|
def is_on(hass, entity_id):
|
||||||
|
@ -41,10 +39,10 @@ def is_on(hass, entity_id):
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
if state:
|
if state:
|
||||||
group_type = _get_group_type(state.state)
|
group_on, _ = _get_group_on_off(state.state)
|
||||||
|
|
||||||
# If we found a group_type, compare to ON-state
|
# If we found a group_type, compare to ON-state
|
||||||
return group_type and state.state == _GROUP_TYPES[group_type][0]
|
return group_on is not None and state.state == group_on
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -103,64 +101,74 @@ def setup(hass, config):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
|
||||||
def setup_group(hass, name, entity_ids, user_defined=True):
|
def setup_group(hass, name, entity_ids, user_defined=True):
|
||||||
""" Sets up a group state that is the combined state of
|
""" Sets up a group state that is the combined state of
|
||||||
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# In case an iterable is passed in
|
# In case an iterable is passed in
|
||||||
entity_ids = list(entity_ids)
|
entity_ids = list(entity_ids)
|
||||||
|
|
||||||
|
if not entity_ids:
|
||||||
|
logger.error(
|
||||||
|
'Error setting up group %s: no entities passed in to track', name)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
# Loop over the given entities to:
|
# Loop over the given entities to:
|
||||||
# - determine which group type this is (on_off, device_home)
|
# - determine which group type this is (on_off, device_home)
|
||||||
# - if all states exist and have valid states
|
# - determine which states exist and have groupable states
|
||||||
# - retrieve the current state of the group
|
# - determine the current state of the group
|
||||||
errors = []
|
warnings = []
|
||||||
group_type, group_on, group_off, group_state = None, None, None, None
|
group_ids = []
|
||||||
|
group_on, group_off = None, None
|
||||||
|
group_state = False
|
||||||
|
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
# Try to determine group type if we didn't yet
|
# Try to determine group type if we didn't yet
|
||||||
if not group_type and state:
|
if group_on is None and state:
|
||||||
group_type = _get_group_type(state.state)
|
group_on, group_off = _get_group_on_off(state.state)
|
||||||
|
|
||||||
if group_type:
|
if group_on is None:
|
||||||
group_on, group_off = _GROUP_TYPES[group_type]
|
|
||||||
group_state = group_off
|
|
||||||
|
|
||||||
else:
|
|
||||||
# We did not find a matching group_type
|
# We did not find a matching group_type
|
||||||
errors.append(
|
warnings.append(
|
||||||
"Entity {} has ungroupable state '{}'".format(
|
"Entity {} has ungroupable state '{}'".format(
|
||||||
name, state.state))
|
name, state.state))
|
||||||
|
|
||||||
# Stop check all other entity IDs and report as error
|
continue
|
||||||
break
|
|
||||||
|
|
||||||
# Check if entity exists
|
# Check if entity exists
|
||||||
if not state:
|
if not state:
|
||||||
errors.append("Entity {} does not exist".format(entity_id))
|
warnings.append("Entity {} does not exist".format(entity_id))
|
||||||
|
|
||||||
# Check if entity is valid state
|
# Check if entity is invalid state
|
||||||
elif state.state != group_off and state.state != group_on:
|
elif state.state != group_off and state.state != group_on:
|
||||||
|
|
||||||
errors.append("State of {} is {} (expected: {} or {})".format(
|
warnings.append("State of {} is {} (expected: {} or {})".format(
|
||||||
entity_id, state.state, group_off, group_on))
|
entity_id, state.state, group_off, group_on))
|
||||||
|
|
||||||
# Keep track of the group state to init later on
|
# We have a valid group state
|
||||||
elif state.state == group_on:
|
else:
|
||||||
group_state = group_on
|
group_ids.append(entity_id)
|
||||||
|
|
||||||
if group_type is None and not errors:
|
# Keep track of the group state to init later on
|
||||||
errors.append('Unable to determine group type for {}'.format(name))
|
group_state = group_state or state.state == group_on
|
||||||
|
|
||||||
if errors:
|
# If none of the entities could be found during setup
|
||||||
logging.getLogger(__name__).error(
|
if not group_ids:
|
||||||
"Error setting up group %s: %s", name, ", ".join(errors))
|
logger.error('Unable to find any entities to track for group %s', name)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
elif warnings:
|
||||||
|
logger.warning(
|
||||||
|
'Warnings during setting up group %s: %s',
|
||||||
|
name, ", ".join(warnings))
|
||||||
|
|
||||||
group_entity_id = ENTITY_ID_FORMAT.format(util.slugify(name))
|
group_entity_id = ENTITY_ID_FORMAT.format(util.slugify(name))
|
||||||
|
state = group_on if group_state else group_off
|
||||||
state_attr = {ATTR_ENTITY_ID: entity_ids, ATTR_AUTO: not user_defined}
|
state_attr = {ATTR_ENTITY_ID: entity_ids, ATTR_AUTO: not user_defined}
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -182,14 +190,14 @@ def setup_group(hass, name, entity_ids, user_defined=True):
|
||||||
|
|
||||||
# Check if any of the other states is still on
|
# Check if any of the other states is still on
|
||||||
if not any([hass.states.is_state(ent_id, group_on)
|
if not any([hass.states.is_state(ent_id, group_on)
|
||||||
for ent_id in entity_ids
|
for ent_id in group_ids
|
||||||
if entity_id != ent_id]):
|
if entity_id != ent_id]):
|
||||||
hass.states.set(group_entity_id, group_off, state_attr)
|
hass.states.set(group_entity_id, group_off, state_attr)
|
||||||
|
|
||||||
_GROUPS[group_entity_id] = hass.states.track_change(
|
_GROUPS[group_entity_id] = hass.states.track_change(
|
||||||
entity_ids, update_group_state)
|
group_ids, update_group_state)
|
||||||
|
|
||||||
hass.states.set(group_entity_id, group_state, state_attr)
|
hass.states.set(group_entity_id, state, state_attr)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue