2016-03-09 22:49:54 +00:00
|
|
|
"""Helpers for components that manage entities."""
|
2015-11-28 23:55:01 +00:00
|
|
|
from threading import Lock
|
|
|
|
|
2016-09-07 13:59:16 +00:00
|
|
|
from homeassistant import config as conf_util
|
|
|
|
from homeassistant.bootstrap import (prepare_setup_platform,
|
|
|
|
prepare_setup_component)
|
2016-04-23 04:34:49 +00:00
|
|
|
from homeassistant.const import (
|
|
|
|
ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE,
|
|
|
|
DEVICE_DEFAULT_NAME)
|
2016-09-07 13:59:16 +00:00
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
|
|
from homeassistant.loader import get_component
|
2016-06-12 00:43:13 +00:00
|
|
|
from homeassistant.helpers import config_per_platform, discovery
|
2016-01-24 07:00:46 +00:00
|
|
|
from homeassistant.helpers.entity import generate_entity_id
|
2015-08-03 15:08:13 +00:00
|
|
|
from homeassistant.helpers.event import track_utc_time_change
|
2016-01-24 06:57:14 +00:00
|
|
|
from homeassistant.helpers.service import extract_entity_ids
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
DEFAULT_SCAN_INTERVAL = 15
|
|
|
|
|
|
|
|
|
|
|
|
class EntityComponent(object):
|
2016-01-31 02:55:52 +00:00
|
|
|
"""Helper class that will help a component manage its entities."""
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
# pylint: disable=too-many-instance-attributes
|
|
|
|
# pylint: disable=too-many-arguments
|
|
|
|
def __init__(self, logger, domain, hass,
|
2016-06-12 00:43:13 +00:00
|
|
|
scan_interval=DEFAULT_SCAN_INTERVAL, group_name=None):
|
2016-01-31 02:55:52 +00:00
|
|
|
"""Initialize an entity component."""
|
2015-03-22 01:49:30 +00:00
|
|
|
self.logger = logger
|
|
|
|
self.hass = hass
|
|
|
|
|
|
|
|
self.domain = domain
|
|
|
|
self.entity_id_format = domain + '.{}'
|
|
|
|
self.scan_interval = scan_interval
|
|
|
|
self.group_name = group_name
|
|
|
|
|
|
|
|
self.entities = {}
|
|
|
|
self.group = None
|
|
|
|
|
2015-05-15 04:36:12 +00:00
|
|
|
self.config = None
|
2015-11-28 23:55:01 +00:00
|
|
|
self.lock = Lock()
|
2015-05-15 04:36:12 +00:00
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
self._platforms = {
|
|
|
|
'core': EntityPlatform(self, self.scan_interval, None),
|
|
|
|
}
|
|
|
|
self.add_entities = self._platforms['core'].add_entities
|
2016-01-31 08:55:46 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
def setup(self, config):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Set up a full entity component.
|
2016-01-31 02:55:52 +00:00
|
|
|
|
|
|
|
Loads the platforms from the config and will listen for supported
|
|
|
|
discovered platforms.
|
2015-03-22 01:49:30 +00:00
|
|
|
"""
|
2015-05-15 04:36:12 +00:00
|
|
|
self.config = config
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
2016-03-28 01:48:51 +00:00
|
|
|
for p_type, p_config in config_per_platform(config, self.domain):
|
2015-05-15 04:36:12 +00:00
|
|
|
self._setup_platform(p_type, p_config)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-05-12 04:58:22 +00:00
|
|
|
# Generic discovery listener for loading platform dynamically
|
|
|
|
# Refer to: homeassistant.components.discovery.load_platform()
|
2016-06-12 00:43:13 +00:00
|
|
|
def component_platform_discovered(platform, info):
|
2016-05-12 04:58:22 +00:00
|
|
|
"""Callback to load a platform."""
|
2016-06-12 00:43:13 +00:00
|
|
|
self._setup_platform(platform, {}, info)
|
|
|
|
|
|
|
|
discovery.listen_platform(self.hass, self.domain,
|
|
|
|
component_platform_discovered)
|
2016-05-10 05:48:03 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
def extract_from_service(self, service):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Extract all known entities from a service call.
|
2016-01-31 02:55:52 +00:00
|
|
|
|
|
|
|
Will return all entities if no entities specified in call.
|
|
|
|
Will return an empty list if entities specified but unknown.
|
2015-03-22 01:49:30 +00:00
|
|
|
"""
|
2015-11-28 23:55:01 +00:00
|
|
|
with self.lock:
|
|
|
|
if ATTR_ENTITY_ID not in service.data:
|
|
|
|
return list(self.entities.values())
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
return [self.entities[entity_id] for entity_id
|
|
|
|
in extract_entity_ids(self.hass, service)
|
|
|
|
if entity_id in self.entities]
|
|
|
|
|
2015-05-15 04:36:12 +00:00
|
|
|
def _setup_platform(self, platform_type, platform_config,
|
2015-05-12 05:23:20 +00:00
|
|
|
discovery_info=None):
|
2016-01-31 02:55:52 +00:00
|
|
|
"""Setup a platform for this component."""
|
2015-05-12 05:23:20 +00:00
|
|
|
platform = prepare_setup_platform(
|
2015-05-15 04:36:12 +00:00
|
|
|
self.hass, self.config, self.domain, platform_type)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-04-18 05:07:53 +00:00
|
|
|
if platform is None:
|
|
|
|
return
|
|
|
|
|
2016-04-03 03:10:57 +00:00
|
|
|
# Config > Platform > Component
|
2016-09-04 15:15:52 +00:00
|
|
|
scan_interval = (platform_config.get(CONF_SCAN_INTERVAL) or
|
|
|
|
getattr(platform, 'SCAN_INTERVAL', None) or
|
|
|
|
self.scan_interval)
|
2016-04-23 04:34:49 +00:00
|
|
|
entity_namespace = platform_config.get(CONF_ENTITY_NAMESPACE)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
key = (platform_type, scan_interval, entity_namespace)
|
|
|
|
|
|
|
|
if key not in self._platforms:
|
|
|
|
self._platforms[key] = EntityPlatform(self, scan_interval,
|
|
|
|
entity_namespace)
|
|
|
|
entity_platform = self._platforms[key]
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
try:
|
2016-09-04 15:15:52 +00:00
|
|
|
platform.setup_platform(self.hass, platform_config,
|
|
|
|
entity_platform.add_entities,
|
|
|
|
discovery_info)
|
2016-04-03 03:10:57 +00:00
|
|
|
|
|
|
|
self.hass.config.components.append(
|
|
|
|
'{}.{}'.format(self.domain, platform_type))
|
2015-03-22 05:21:57 +00:00
|
|
|
except Exception: # pylint: disable=broad-except
|
|
|
|
self.logger.exception(
|
2015-05-12 05:23:38 +00:00
|
|
|
'Error while setting up platform %s', platform_type)
|
2015-09-10 06:37:15 +00:00
|
|
|
|
2016-04-23 04:34:49 +00:00
|
|
|
def add_entity(self, entity, platform=None):
|
2016-01-31 08:55:46 +00:00
|
|
|
"""Add entity to component."""
|
|
|
|
if entity is None or entity in self.entities.values():
|
|
|
|
return False
|
|
|
|
|
|
|
|
entity.hass = self.hass
|
|
|
|
|
|
|
|
if getattr(entity, 'entity_id', None) is None:
|
2016-04-23 04:34:49 +00:00
|
|
|
object_id = entity.name or DEVICE_DEFAULT_NAME
|
|
|
|
|
|
|
|
if platform is not None and platform.entity_namespace is not None:
|
|
|
|
object_id = '{} {}'.format(platform.entity_namespace,
|
|
|
|
object_id)
|
|
|
|
|
2016-01-31 08:55:46 +00:00
|
|
|
entity.entity_id = generate_entity_id(
|
2016-04-23 04:34:49 +00:00
|
|
|
self.entity_id_format, object_id,
|
2016-01-31 08:55:46 +00:00
|
|
|
self.entities.keys())
|
|
|
|
|
|
|
|
self.entities[entity.entity_id] = entity
|
|
|
|
entity.update_ha_state()
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def update_group(self):
|
|
|
|
"""Set up and/or update component group."""
|
|
|
|
if self.group is None and self.group_name is not None:
|
2016-09-07 13:59:16 +00:00
|
|
|
group = get_component('group')
|
2016-01-31 08:55:46 +00:00
|
|
|
self.group = group.Group(self.hass, self.group_name,
|
|
|
|
user_defined=False)
|
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
self.group.update_tracked_entity_ids(self.entities.keys())
|
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
def reset(self):
|
|
|
|
"""Remove entities and reset the entity component to initial values."""
|
|
|
|
with self.lock:
|
|
|
|
for platform in self._platforms.values():
|
|
|
|
platform.reset()
|
|
|
|
|
|
|
|
self._platforms = {
|
|
|
|
'core': self._platforms['core']
|
|
|
|
}
|
|
|
|
self.entities = {}
|
|
|
|
self.config = None
|
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
self.group.stop()
|
|
|
|
self.group = None
|
|
|
|
|
2016-09-07 13:59:16 +00:00
|
|
|
def prepare_reload(self):
|
|
|
|
"""Prepare reloading this entity component."""
|
|
|
|
try:
|
|
|
|
path = conf_util.find_config_file(self.hass.config.config_dir)
|
|
|
|
conf = conf_util.load_yaml_config_file(path)
|
|
|
|
except HomeAssistantError as err:
|
|
|
|
self.logger.error(err)
|
|
|
|
return None
|
|
|
|
|
|
|
|
conf = prepare_setup_component(self.hass, conf, self.domain)
|
|
|
|
|
|
|
|
if conf is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
self.reset()
|
|
|
|
return conf
|
|
|
|
|
2016-01-31 08:55:46 +00:00
|
|
|
|
|
|
|
class EntityPlatform(object):
|
|
|
|
"""Keep track of entities for a single platform."""
|
|
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
2016-04-23 04:34:49 +00:00
|
|
|
def __init__(self, component, scan_interval, entity_namespace):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Initalize the entity platform."""
|
2016-01-31 08:55:46 +00:00
|
|
|
self.component = component
|
|
|
|
self.scan_interval = scan_interval
|
2016-04-23 04:34:49 +00:00
|
|
|
self.entity_namespace = entity_namespace
|
2016-01-31 08:55:46 +00:00
|
|
|
self.platform_entities = []
|
2016-09-04 15:15:52 +00:00
|
|
|
self._unsub_polling = None
|
2016-01-31 08:55:46 +00:00
|
|
|
|
|
|
|
def add_entities(self, new_entities):
|
|
|
|
"""Add entities for a single platform."""
|
|
|
|
with self.component.lock:
|
|
|
|
for entity in new_entities:
|
2016-04-23 04:34:49 +00:00
|
|
|
if self.component.add_entity(entity, self):
|
2016-01-31 08:55:46 +00:00
|
|
|
self.platform_entities.append(entity)
|
|
|
|
|
|
|
|
self.component.update_group()
|
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
if self._unsub_polling is not None or \
|
2016-01-31 08:55:46 +00:00
|
|
|
not any(entity.should_poll for entity
|
|
|
|
in self.platform_entities):
|
|
|
|
return
|
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
self._unsub_polling = track_utc_time_change(
|
2016-01-31 08:55:46 +00:00
|
|
|
self.component.hass, self._update_entity_states,
|
|
|
|
second=range(0, 60, self.scan_interval))
|
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
def reset(self):
|
|
|
|
"""Remove all entities and reset data."""
|
|
|
|
for entity in self.platform_entities:
|
|
|
|
entity.remove()
|
|
|
|
if self._unsub_polling is not None:
|
|
|
|
self._unsub_polling()
|
|
|
|
self._unsub_polling = None
|
|
|
|
|
2016-01-31 08:55:46 +00:00
|
|
|
def _update_entity_states(self, now):
|
|
|
|
"""Update the states of all the polling entities."""
|
|
|
|
with self.component.lock:
|
|
|
|
# We copy the entities because new entities might be detected
|
|
|
|
# during state update causing deadlocks.
|
|
|
|
entities = list(entity for entity in self.platform_entities
|
|
|
|
if entity.should_poll)
|
|
|
|
|
|
|
|
for entity in entities:
|
|
|
|
entity.update_ha_state(True)
|