core/homeassistant/bootstrap.py

338 lines
10 KiB
Python
Raw Normal View History

"""
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 sys
import logging
from collections import defaultdict
2015-08-17 03:44:46 +00:00
import homeassistant.core as core
import homeassistant.util.dt as date_util
import homeassistant.util.package as pkg_util
import homeassistant.util.location as loc_util
import homeassistant.config as config_util
import homeassistant.loader as loader
import homeassistant.components as core_components
import homeassistant.components.group as group
2015-04-23 13:41:41 +00:00
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_CUSTOMIZE,
TEMP_CELCIUS, TEMP_FAHRENHEIT)
2014-01-24 05:34:08 +00:00
_LOGGER = logging.getLogger(__name__)
2015-05-12 05:23:38 +00:00
ATTR_COMPONENT = 'component'
PLATFORM_FORMAT = '{}.{}'
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 _handle_requirements(hass, component, name):
""" Installs requirements for component. """
2015-08-04 20:21:09 +00:00
if not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS:
if not pkg_util.install_package(req, target=hass.config.path('lib')):
2015-08-04 20:21:09 +00:00
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
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(
2015-05-12 05:23:38 +00:00
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return False
if not _handle_requirements(hass, component, domain):
return False
try:
2015-08-03 15:05:33 +00:00
if not component.setup(hass, config):
2015-05-12 05:23:38 +00:00
_LOGGER.error('component %s failed to initialize', domain)
2015-08-03 15:05:33 +00:00
return False
except Exception: # pylint: disable=broad-except
2015-05-12 05:23:38 +00:00
_LOGGER.exception('Error during setup of component %s', domain)
2015-08-03 15:05:33 +00:00
return False
hass.config.components.append(component.DOMAIN)
2015-08-03 15:05:33 +00:00
# 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
def prepare_setup_platform(hass, config, domain, platform_name):
""" Loads a platform and makes sure dependencies are setup. """
_ensure_loader_prepared(hass)
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
platform = loader.get_component(platform_path)
# Not found
if platform is None:
return None
# Already loaded
elif platform_path in hass.config.components:
return platform
# Load dependencies
if hasattr(platform, 'DEPENDENCIES'):
for component in platform.DEPENDENCIES:
if not setup_component(hass, component, config):
_LOGGER.error(
'Unable to prepare setup for platform %s because '
'dependency %s could not be initialized', platform_path,
component)
return None
if not _handle_requirements(hass, platform, platform_path):
return None
return platform
def mount_local_lib_path(config_dir):
""" Add local library to Python Path """
sys.path.insert(0, os.path.join(config_dir, 'lib'))
2014-10-25 06:44:00 +00:00
# pylint: disable=too-many-branches, too-many-statements
def from_config_dict(config, hass=None, config_dir=None):
"""
Tries to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies.
"""
if hass is None:
2015-08-17 03:44:46 +00:00
hass = core.HomeAssistant()
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
2015-08-17 03:44:46 +00:00
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
enable_logging(hass)
_ensure_loader_prepared(hass)
2014-11-28 23:34:42 +00:00
# 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
2015-02-28 19:31:26 +00:00
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()
2015-08-17 03:44:46 +00:00
if ' ' not in key and key != core.DOMAIN)
if not core_components.setup(hass, config):
2015-05-12 05:23:38 +00:00
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
2015-05-12 05:23:38 +00:00
_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:
2015-08-17 03:44:46 +00:00
hass = core.HomeAssistant()
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
config_dict = config_util.load_config_file(config_path)
return from_config_dict(config_dict, hass)
def enable_logging(hass):
""" Setup the logging for home assistant. """
logging.basicConfig(level=logging.INFO)
2015-06-11 13:01:11 +00:00
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s")
try:
from colorlog import ColoredFormatter
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
fmt,
datefmt='%y-%m-%d %H:%M:%S',
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
))
except ImportError:
2015-08-17 03:44:46 +00:00
_LOGGER.warning(
"Colorlog package not found, console coloring disabled")
# Log errors to a file if we have write access to file or config dir
2015-05-12 05:23:38 +00:00
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',
2015-06-11 13:01:11 +00:00
datefmt='%y-%m-%d %H:%M:%S'))
logging.getLogger('').addHandler(err_handler)
else:
_LOGGER.error(
2015-05-12 05:23:38 +00:00
'Unable to setup error log %s (access denied)', err_log_path)
2015-01-30 16:26:06 +00:00
def process_ha_core_config(hass, config):
""" Processes the [homeassistant] section from the config. """
hac = hass.config
def set_time_zone(time_zone_str):
""" Helper method to set time zone in HA. """
if time_zone_str is None:
return
time_zone = date_util.get_time_zone(time_zone_str)
if time_zone:
hac.time_zone = time_zone
date_util.set_default_time_zone(time_zone)
else:
2015-05-12 05:23:38 +00:00
_LOGGER.error('Received invalid time zone %s', time_zone_str)
for key, attr in ((CONF_LATITUDE, 'latitude'),
(CONF_LONGITUDE, 'longitude'),
(CONF_NAME, 'location_name')):
if key in config:
setattr(hac, attr, config[key])
set_time_zone(config.get(CONF_TIME_ZONE))
2015-06-03 04:31:50 +00:00
customize = config.get(CONF_CUSTOMIZE)
if isinstance(customize, dict):
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items():
if not isinstance(attrs, dict):
continue
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
if CONF_TEMPERATURE_UNIT in config:
unit = config[CONF_TEMPERATURE_UNIT]
if unit == 'C':
hac.temperature_unit = TEMP_CELCIUS
elif unit == 'F':
hac.temperature_unit = TEMP_FAHRENHEIT
# If we miss some of the needed values, auto detect them
if None not in (
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
return
_LOGGER.info('Auto detecting location and temperature unit')
info = loc_util.detect_location_info()
if info is None:
_LOGGER.error('Could not detect location information')
return
if hac.latitude is None and hac.longitude is None:
hac.latitude = info.latitude
hac.longitude = info.longitude
if hac.temperature_unit is None:
if info.use_fahrenheit:
hac.temperature_unit = TEMP_FAHRENHEIT
else:
hac.temperature_unit = TEMP_CELCIUS
if hac.location_name is None:
hac.location_name = info.city
if hac.time_zone is None:
set_time_zone(info.time_zone)
def _ensure_loader_prepared(hass):
""" Ensure Home Assistant loader is prepared. """
if not loader.PREPARED:
loader.prepare(hass)