""" homeassistant.helpers.entity_component ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides helpers for components that manage entities. """ from threading import Lock from homeassistant.bootstrap import prepare_setup_platform from homeassistant.helpers import ( generate_entity_id, config_per_platform, extract_entity_ids) from homeassistant.helpers.event import track_utc_time_change from homeassistant.components import group, discovery from homeassistant.const import ATTR_ENTITY_ID DEFAULT_SCAN_INTERVAL = 15 class EntityComponent(object): # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-arguments """ Helper class that will help a component manage its entities. """ def __init__(self, logger, domain, hass, scan_interval=DEFAULT_SCAN_INTERVAL, discovery_platforms=None, group_name=None): self.logger = logger self.hass = hass self.domain = domain self.entity_id_format = domain + '.{}' self.scan_interval = scan_interval self.discovery_platforms = discovery_platforms self.group_name = group_name self.entities = {} self.group = None self.is_polling = False self.config = None self.lock = Lock() def setup(self, config): """ Sets up a full entity component: - Loads the platforms from the config - Will listen for supported discovered platforms """ self.config = config # Look in config for Domain, Domain 2, Domain 3 etc and load them for p_type, p_config in \ config_per_platform(config, self.domain, self.logger): self._setup_platform(p_type, p_config) if self.discovery_platforms: discovery.listen(self.hass, self.discovery_platforms.keys(), self._entity_discovered) def add_entities(self, new_entities): """ Takes in a list of new entities. For each entity will see if it already exists. If not, will add it, set it up and push the first state. """ with self.lock: for entity in new_entities: if entity is None or entity in self.entities.values(): continue entity.hass = self.hass if getattr(entity, 'entity_id', None) is None: entity.entity_id = generate_entity_id( self.entity_id_format, entity.name, self.entities.keys()) self.entities[entity.entity_id] = entity entity.update_ha_state() if self.group is None and self.group_name is not None: 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()) if self.is_polling or \ not any(entity.should_poll for entity in self.entities.values()): return self.is_polling = True track_utc_time_change( self.hass, self._update_entity_states, second=range(0, 60, self.scan_interval)) def extract_from_service(self, service): """ Takes a service and extracts all known entities. Will return all if no entity IDs given in service. """ with self.lock: if ATTR_ENTITY_ID not in service.data: return list(self.entities.values()) return [self.entities[entity_id] for entity_id in extract_entity_ids(self.hass, service) if entity_id in self.entities] def _update_entity_states(self, now): """ Update the states of all the entities. """ with self.lock: # We copy the entities because new entities might be detected # during state update causing deadlocks. entities = list(entity for entity in self.entities.values() if entity.should_poll) self.logger.info("Updating %s entities", self.domain) for entity in entities: entity.update_ha_state(True) def _entity_discovered(self, service, info): """ Called when a entity is discovered. """ if service not in self.discovery_platforms: return self._setup_platform(self.discovery_platforms[service], {}, info) def _setup_platform(self, platform_type, platform_config, discovery_info=None): """ Tries to setup a platform for this component. """ platform = prepare_setup_platform( self.hass, self.config, self.domain, platform_type) if platform is None: return try: platform.setup_platform( self.hass, platform_config, self.add_entities, discovery_info) except Exception: # pylint: disable=broad-except self.logger.exception( 'Error while setting up platform %s', platform_type) return platform_name = '{}.{}'.format(self.domain, platform_type) self.hass.config.components.append(platform_name)