""" homeassistant.bootstrap ~~~~~~~~~~~~~~~~~~~~~~~ Provides methods to bootstrap a home assistant instance. Each method will return a tuple (bus, statemachine). After bootstrapping you can add your own components or start by calling homeassistant.start_home_assistant(bus) """ import os import configparser import yaml import io import logging from collections import defaultdict import homeassistant import homeassistant.loader as loader import homeassistant.components as core_components import homeassistant.components.group as group from homeassistant.helpers.entity import Entity from homeassistant.const import ( EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_VISIBILITY, CONF_DECORATE, TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_ENTITY_PICTURE, ATTR_HIDDEN) _LOGGER = logging.getLogger(__name__) ATTR_COMPONENT = "component" def setup_component(hass, domain, config=None): """ Setup a component and all its dependencies. """ if domain in hass.config.components: return True _ensure_loader_prepared(hass) if config is None: config = defaultdict(dict) components = loader.load_order_component(domain) # OrderedSet is empty if component or dependencies could not be resolved if not components: return False for component in components: if component in hass.config.components: continue if not _setup_component(hass, component, config): return False return True def _setup_component(hass, domain, config): """ Setup a component for Home Assistant. """ component = loader.get_component(domain) missing_deps = [dep for dep in component.DEPENDENCIES if dep not in hass.config.components] if missing_deps: _LOGGER.error( "Not initializing %s because not all dependencies loaded: %s", domain, ", ".join(missing_deps)) return False try: if component.setup(hass, config): hass.config.components.append(component.DOMAIN) # Assumption: if a component does not depend on groups # it communicates with devices if group.DOMAIN not in component.DEPENDENCIES: hass.pool.add_worker() hass.bus.fire( EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) return True else: _LOGGER.error("component %s failed to initialize", domain) except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during setup of component %s", domain) return False # pylint: disable=too-many-branches, too-many-statements def from_config_dict(config, hass=None): """ Tries to configure Home Assistant from a config dict. Dynamically loads required components and its dependencies. """ if hass is None: hass = homeassistant.HomeAssistant() process_ha_core_config(hass, config.get(homeassistant.DOMAIN, {})) enable_logging(hass) _ensure_loader_prepared(hass) # Make a copy because we are mutating it. # Convert it to defaultdict so components can always have config dict # Convert values to dictionaries if they are None config = defaultdict( dict, {key: value or {} for key, value in config.items()}) # Filter out the repeating and common config section [homeassistant] components = (key for key in config.keys() if ' ' not in key and key != homeassistant.DOMAIN) if not core_components.setup(hass, config): _LOGGER.error("Home Assistant core failed to initialize. " "Further initialization aborted.") return hass _LOGGER.info("Home Assistant core initialized") # Setup the components for domain in loader.load_order_components(components): _setup_component(hass, domain, config) return hass def from_config_file(config_path, hass=None): """ Reads the configuration file and tries to start all the required functionality. Will add functionality to 'hass' parameter if given, instantiates a new Home Assistant object if 'hass' is not given. """ if hass is None: hass = homeassistant.HomeAssistant() # Set config dir to directory holding config file hass.config.config_dir = os.path.abspath(os.path.dirname(config_path)) config_dict = {} # check config file type if os.path.splitext(config_path)[1] == '.yaml': # Read yaml config_dict = yaml.load(io.open(config_path, 'r')) # If YAML file was empty if config_dict is None: config_dict = {} else: # Read config config = configparser.ConfigParser() config.read(config_path) for section in config.sections(): config_dict[section] = {} for key, val in config.items(section): config_dict[section][key] = val return from_config_dict(config_dict, hass) def enable_logging(hass): """ Setup the logging for home assistant. """ logging.basicConfig(level=logging.INFO) # Log errors to a file if we have write access to file or config dir err_log_path = hass.config.path("home-assistant.log") err_path_exists = os.path.isfile(err_log_path) # Check if we can write to the error log if it exists or that # we can create files in the containing directory if not. if (err_path_exists and os.access(err_log_path, os.W_OK)) or \ (not err_path_exists and os.access(hass.config.config_dir, os.W_OK)): err_handler = logging.FileHandler( err_log_path, mode='w', delay=True) err_handler.setLevel(logging.WARNING) err_handler.setFormatter( logging.Formatter('%(asctime)s %(name)s: %(message)s', datefmt='%H:%M %d-%m-%y')) logging.getLogger('').addHandler(err_handler) else: _LOGGER.error( "Unable to setup error log %s (access denied)", err_log_path) def process_ha_core_config(hass, config): """ Processes the [homeassistant] section from the config. """ for key, attr in ((CONF_LATITUDE, 'latitude'), (CONF_LONGITUDE, 'longitude'), (CONF_NAME, 'location_name'), (CONF_TIME_ZONE, 'time_zone')): if key in config: setattr(hass.config, attr, config[key]) for entity_id, hidden in config.get(CONF_VISIBILITY, {}).items(): Entity.overwrite_attribute(entity_id, ATTR_HIDDEN, hidden == 'hide') for entity_id, image in config.get(CONF_DECORATE, {}).items(): Entity.overwrite_attribute(entity_id, ATTR_ENTITY_PICTURE, image) if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] if unit == 'C': hass.config.temperature_unit = TEMP_CELCIUS elif unit == 'F': hass.config.temperature_unit = TEMP_FAHRENHEIT hass.config.auto_detect() def _ensure_loader_prepared(hass): """ Ensure Home Assistant loader is prepared. """ if not loader.PREPARED: loader.prepare(hass)