A major change to the bootstrapping of Home Assistant decoupling the knowledge in bootstrap for a more dynamic approach. This refactoring also prepares the code for different configuration backends and the loading components from different places.
192 lines
5.9 KiB
Python
192 lines
5.9 KiB
Python
"""
|
|
homeassistant.components.groups
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Provides functionality to group devices that can be turned on or off.
|
|
"""
|
|
|
|
import logging
|
|
|
|
import homeassistant.util as util
|
|
from homeassistant.components import (STATE_ON, STATE_OFF,
|
|
STATE_HOME, STATE_NOT_HOME,
|
|
ATTR_ENTITY_ID)
|
|
|
|
DOMAIN = "group"
|
|
DEPENDENCIES = []
|
|
|
|
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
|
|
|
_GROUP_TYPES = {
|
|
"on_off": (STATE_ON, STATE_OFF),
|
|
"home_not_home": (STATE_HOME, STATE_NOT_HOME)
|
|
}
|
|
|
|
|
|
def _get_group_type(state):
|
|
""" Determine the group type based on the given group type. """
|
|
for group_type, states in _GROUP_TYPES.items():
|
|
if state in states:
|
|
return group_type
|
|
|
|
return None
|
|
|
|
|
|
def is_on(hass, entity_id):
|
|
""" Returns if the group state is in its ON-state. """
|
|
state = hass.states.get(entity_id)
|
|
|
|
if state:
|
|
group_type = _get_group_type(state.state)
|
|
|
|
# If we found a group_type, compare to ON-state
|
|
return group_type and state.state == _GROUP_TYPES[group_type][0]
|
|
|
|
return False
|
|
|
|
|
|
def expand_entity_ids(hass, entity_ids):
|
|
""" Returns the given list of entity ids and expands group ids into
|
|
the entity ids it represents if found. """
|
|
found_ids = []
|
|
|
|
for entity_id in entity_ids:
|
|
try:
|
|
# If entity_id points at a group, expand it
|
|
domain, _ = util.split_entity_id(entity_id)
|
|
|
|
if domain == DOMAIN:
|
|
found_ids.extend(
|
|
ent_id for ent_id
|
|
in get_entity_ids(hass, entity_id)
|
|
if ent_id not in found_ids)
|
|
|
|
else:
|
|
if entity_id not in found_ids:
|
|
found_ids.append(entity_id)
|
|
|
|
except AttributeError:
|
|
# Raised by util.split_entity_id if entity_id is not a string
|
|
pass
|
|
|
|
return found_ids
|
|
|
|
|
|
def get_entity_ids(hass, entity_id, domain_filter=None):
|
|
""" Get the entity ids that make up this group. """
|
|
try:
|
|
entity_ids = hass.states.get(entity_id).attributes[ATTR_ENTITY_ID]
|
|
|
|
if domain_filter:
|
|
return [entity_id for entity_id in entity_ids
|
|
if entity_id.startswith(domain_filter)]
|
|
else:
|
|
return entity_ids
|
|
|
|
except (AttributeError, KeyError):
|
|
# AttributeError if state did not exist
|
|
# KeyError if key did not exist in attributes
|
|
return []
|
|
|
|
|
|
# pylint: disable=too-many-branches, too-many-locals
|
|
def setup(hass, config):
|
|
""" Sets up all groups found definded in the configuration. """
|
|
|
|
for name, entity_ids in config[DOMAIN].items():
|
|
entity_ids = entity_ids.split(",")
|
|
|
|
setup_group(hass, name, entity_ids)
|
|
|
|
return True
|
|
|
|
|
|
def setup_group(hass, name, entity_ids):
|
|
""" Sets up a group state that is the combined state of
|
|
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Loop over the given entities to:
|
|
# - determine which group type this is (on_off, device_home)
|
|
# - if all states exist and have valid states
|
|
# - retrieve the current state of the group
|
|
errors = []
|
|
group_type, group_on, group_off, group_state = None, None, None, None
|
|
|
|
for entity_id in entity_ids:
|
|
state = hass.states.get(entity_id)
|
|
|
|
# Try to determine group type if we didn't yet
|
|
if not group_type and state:
|
|
group_type = _get_group_type(state.state)
|
|
|
|
if group_type:
|
|
group_on, group_off = _GROUP_TYPES[group_type]
|
|
group_state = group_off
|
|
|
|
else:
|
|
# We did not find a matching group_type
|
|
errors.append(
|
|
"Entity {} has ungroupable state '{}'".format(
|
|
name, state.state))
|
|
|
|
# Stop check all other entity IDs and report as error
|
|
break
|
|
|
|
# Check if entity exists
|
|
if not state:
|
|
errors.append("Entity {} does not exist".format(entity_id))
|
|
|
|
# Check if entity is valid state
|
|
elif state.state != group_off and state.state != group_on:
|
|
|
|
errors.append("State of {} is {} (expected: {}, {})".format(
|
|
entity_id, state.state, group_off, group_on))
|
|
|
|
# Keep track of the group state to init later on
|
|
elif state.state == group_on:
|
|
group_state = group_on
|
|
|
|
if group_type is None and not errors:
|
|
errors.append('Unable to determine group type for {}'.format(name))
|
|
|
|
if errors:
|
|
logger.error("Error setting up group {}: {}".format(
|
|
name, ", ".join(errors)))
|
|
|
|
return False
|
|
|
|
else:
|
|
group_entity_id = ENTITY_ID_FORMAT.format(name)
|
|
state_attr = {ATTR_ENTITY_ID: entity_ids}
|
|
|
|
# pylint: disable=unused-argument
|
|
def update_group_state(entity_id, old_state, new_state):
|
|
""" Updates the group state based on a state change by
|
|
a tracked entity. """
|
|
|
|
cur_gr_state = hass.states.get(group_entity_id).state
|
|
|
|
# if cur_gr_state = OFF and new_state = ON: set ON
|
|
# if cur_gr_state = ON and new_state = OFF: research
|
|
# else: ignore
|
|
|
|
if cur_gr_state == group_off and new_state.state == group_on:
|
|
|
|
hass.states.set(group_entity_id, group_on, state_attr)
|
|
|
|
elif cur_gr_state == group_on and new_state.state == group_off:
|
|
|
|
# Check if any of the other states is still on
|
|
if not any([hass.states.is_state(ent_id, group_on)
|
|
for ent_id in entity_ids
|
|
if entity_id != ent_id]):
|
|
hass.states.set(group_entity_id, group_off, state_attr)
|
|
|
|
for entity_id in entity_ids:
|
|
hass.track_state_change(entity_id, update_group_state)
|
|
|
|
hass.states.set(group_entity_id, group_state, state_attr)
|
|
|
|
return True
|