2016-02-20 07:20:14 +00:00
|
|
|
"""Provides methods to bootstrap a home assistant instance."""
|
2016-10-27 07:16:23 +00:00
|
|
|
import asyncio
|
2013-10-22 05:06:22 +00:00
|
|
|
import logging
|
2015-09-04 22:22:42 +00:00
|
|
|
import logging.handlers
|
2015-11-15 10:05:46 +00:00
|
|
|
import os
|
|
|
|
import sys
|
2016-11-19 16:18:33 +00:00
|
|
|
from collections import OrderedDict
|
2016-07-28 03:33:49 +00:00
|
|
|
|
|
|
|
from types import ModuleType
|
2016-07-21 05:38:52 +00:00
|
|
|
from typing import Any, Optional, Dict
|
2013-10-13 17:42:22 +00:00
|
|
|
|
2016-03-28 01:48:51 +00:00
|
|
|
import voluptuous as vol
|
2016-08-10 04:19:50 +00:00
|
|
|
from voluptuous.humanize import humanize_error
|
2016-03-28 01:48:51 +00:00
|
|
|
|
2016-02-19 05:27:50 +00:00
|
|
|
import homeassistant.components as core_components
|
2016-09-07 13:59:16 +00:00
|
|
|
from homeassistant.components import persistent_notification
|
2016-06-27 16:02:45 +00:00
|
|
|
import homeassistant.config as conf_util
|
2015-08-17 03:44:46 +00:00
|
|
|
import homeassistant.core as core
|
2017-02-13 05:24:07 +00:00
|
|
|
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
2016-02-19 05:27:50 +00:00
|
|
|
import homeassistant.loader as loader
|
|
|
|
import homeassistant.util.package as pkg_util
|
2016-10-27 07:16:23 +00:00
|
|
|
from homeassistant.util.async import (
|
|
|
|
run_coroutine_threadsafe, run_callback_threadsafe)
|
2016-12-16 23:51:06 +00:00
|
|
|
from homeassistant.util.logging import AsyncHandler
|
2016-08-20 19:39:56 +00:00
|
|
|
from homeassistant.util.yaml import clear_secret_cache
|
2016-06-27 16:02:45 +00:00
|
|
|
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
|
2016-04-09 22:25:01 +00:00
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2016-03-28 01:48:51 +00:00
|
|
|
from homeassistant.helpers import (
|
2016-06-27 16:02:45 +00:00
|
|
|
event_decorators, service, config_per_platform, extract_domain_configs)
|
2017-02-09 05:58:45 +00:00
|
|
|
from homeassistant.helpers.signal import async_register_signal_handling
|
2014-01-24 05:34:08 +00:00
|
|
|
|
2015-01-09 08:07:58 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2015-05-12 05:23:38 +00:00
|
|
|
ATTR_COMPONENT = 'component'
|
2015-02-14 06:49:56 +00:00
|
|
|
|
2015-11-07 09:44:02 +00:00
|
|
|
ERROR_LOG_FILENAME = 'home-assistant.log'
|
2016-11-03 02:31:09 +00:00
|
|
|
_PERSISTENT_ERRORS = {}
|
|
|
|
HA_COMPONENT_URL = '[{}](https://home-assistant.io/components/{}/)'
|
2015-05-12 05:23:20 +00:00
|
|
|
|
2015-01-09 08:07:58 +00:00
|
|
|
|
2016-07-28 03:33:49 +00:00
|
|
|
def setup_component(hass: core.HomeAssistant, domain: str,
|
|
|
|
config: Optional[Dict]=None) -> bool:
|
2016-03-07 23:06:04 +00:00
|
|
|
"""Setup a component and all its dependencies."""
|
2016-10-27 07:16:23 +00:00
|
|
|
return run_coroutine_threadsafe(
|
|
|
|
async_setup_component(hass, domain, config), loop=hass.loop).result()
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_setup_component(hass: core.HomeAssistant, domain: str,
|
|
|
|
config: Optional[Dict]=None) -> bool:
|
|
|
|
"""Setup a component and all its dependencies.
|
|
|
|
|
2016-10-28 19:26:52 +00:00
|
|
|
This method is a coroutine.
|
2016-10-27 07:16:23 +00:00
|
|
|
"""
|
2015-03-22 04:10:46 +00:00
|
|
|
if domain in hass.config.components:
|
2016-10-08 18:27:35 +00:00
|
|
|
_LOGGER.debug('Component %s already set up.', domain)
|
2015-03-22 05:02:47 +00:00
|
|
|
return True
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
if not loader.PREPARED:
|
|
|
|
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2015-01-09 08:07:58 +00:00
|
|
|
if config is None:
|
2016-11-19 16:18:33 +00:00
|
|
|
config = {}
|
2015-01-09 08:07:58 +00:00
|
|
|
|
2015-03-22 05:02:47 +00:00
|
|
|
components = loader.load_order_component(domain)
|
|
|
|
|
|
|
|
# OrderedSet is empty if component or dependencies could not be resolved
|
|
|
|
if not components:
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, domain, True)
|
2015-03-22 05:02:47 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
for component in components:
|
2016-10-27 07:16:23 +00:00
|
|
|
res = yield from _async_setup_component(hass, component, config)
|
|
|
|
if not res:
|
2016-10-08 18:27:35 +00:00
|
|
|
_LOGGER.error('Component %s failed to setup', component)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, component, True)
|
2015-03-22 05:02:47 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2016-07-28 03:33:49 +00:00
|
|
|
def _handle_requirements(hass: core.HomeAssistant, component,
|
|
|
|
name: str) -> bool:
|
2016-10-27 07:16:23 +00:00
|
|
|
"""Install the requirements for a component.
|
|
|
|
|
2016-10-28 19:26:52 +00:00
|
|
|
This method needs to run in an executor.
|
2016-10-27 07:16:23 +00:00
|
|
|
"""
|
2015-09-04 21:50:57 +00:00
|
|
|
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
|
2015-08-04 20:21:09 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
for req in component.REQUIREMENTS:
|
2016-04-12 03:07:50 +00:00
|
|
|
if not pkg_util.install_package(req, target=hass.config.path('deps')):
|
2015-08-04 20:21:09 +00:00
|
|
|
_LOGGER.error('Not initializing %s because could not install '
|
|
|
|
'dependency %s', name, req)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, name)
|
2015-08-04 20:21:09 +00:00
|
|
|
return False
|
|
|
|
|
2015-07-07 07:00:21 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def _async_setup_component(hass: core.HomeAssistant,
|
|
|
|
domain: str, config) -> bool:
|
|
|
|
"""Setup a component for Home Assistant.
|
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
2016-10-30 21:18:53 +00:00
|
|
|
# pylint: disable=too-many-return-statements
|
2015-08-30 22:07:37 +00:00
|
|
|
if domain in hass.config.components:
|
|
|
|
return True
|
2015-01-09 08:07:58 +00:00
|
|
|
|
2016-10-29 21:51:17 +00:00
|
|
|
setup_lock = hass.data.get('setup_lock')
|
|
|
|
if setup_lock is None:
|
|
|
|
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
|
2015-03-22 05:02:47 +00:00
|
|
|
|
2016-10-29 21:51:17 +00:00
|
|
|
setup_progress = hass.data.get('setup_progress')
|
|
|
|
if setup_progress is None:
|
|
|
|
setup_progress = hass.data['setup_progress'] = []
|
|
|
|
|
|
|
|
if domain in setup_progress:
|
|
|
|
_LOGGER.error('Attempt made to setup %s during setup of %s',
|
|
|
|
domain, domain)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, domain, True)
|
2016-10-29 21:51:17 +00:00
|
|
|
return False
|
2016-04-03 03:10:57 +00:00
|
|
|
|
2016-10-29 21:51:17 +00:00
|
|
|
try:
|
|
|
|
# Used to indicate to discovery that a setup is ongoing and allow it
|
|
|
|
# to wait till it is done.
|
|
|
|
did_lock = False
|
|
|
|
if not setup_lock.locked():
|
|
|
|
yield from setup_lock.acquire()
|
|
|
|
did_lock = True
|
|
|
|
|
|
|
|
setup_progress.append(domain)
|
2016-10-29 19:54:47 +00:00
|
|
|
config = yield from async_prepare_setup_component(hass, config, domain)
|
2015-01-09 08:07:58 +00:00
|
|
|
|
2016-10-29 19:54:47 +00:00
|
|
|
if config is None:
|
2016-10-27 07:16:23 +00:00
|
|
|
return False
|
2016-10-29 19:54:47 +00:00
|
|
|
|
|
|
|
component = loader.get_component(domain)
|
2016-11-03 02:31:09 +00:00
|
|
|
if component is None:
|
|
|
|
_async_persistent_notification(hass, domain)
|
|
|
|
return False
|
|
|
|
|
2016-10-31 15:47:29 +00:00
|
|
|
async_comp = hasattr(component, 'async_setup')
|
2016-10-29 19:54:47 +00:00
|
|
|
|
|
|
|
try:
|
2016-11-10 17:46:31 +00:00
|
|
|
_LOGGER.info("Setting up %s", domain)
|
2016-10-31 15:47:29 +00:00
|
|
|
if async_comp:
|
2016-10-29 19:54:47 +00:00
|
|
|
result = yield from component.async_setup(hass, config)
|
|
|
|
else:
|
|
|
|
result = yield from hass.loop.run_in_executor(
|
|
|
|
None, component.setup, hass, config)
|
|
|
|
except Exception: # pylint: disable=broad-except
|
|
|
|
_LOGGER.exception('Error during setup of component %s', domain)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, domain, True)
|
2016-02-20 07:20:14 +00:00
|
|
|
return False
|
2016-10-29 21:51:17 +00:00
|
|
|
|
|
|
|
if result is False:
|
|
|
|
_LOGGER.error('component %s failed to initialize', domain)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, domain, True)
|
2016-10-29 21:51:17 +00:00
|
|
|
return False
|
|
|
|
elif result is not True:
|
|
|
|
_LOGGER.error('component %s did not return boolean if setup '
|
|
|
|
'was successful. Disabling component.', domain)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, domain, True)
|
2016-10-29 21:51:17 +00:00
|
|
|
loader.set_component(domain, None)
|
|
|
|
return False
|
2015-08-03 15:05:33 +00:00
|
|
|
|
2017-02-09 18:21:57 +00:00
|
|
|
hass.config.components.add(component.DOMAIN)
|
2016-02-20 07:20:14 +00:00
|
|
|
|
2016-10-29 19:54:47 +00:00
|
|
|
hass.bus.async_fire(
|
|
|
|
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
|
|
|
|
)
|
2016-02-20 07:20:14 +00:00
|
|
|
|
2016-10-29 19:54:47 +00:00
|
|
|
return True
|
|
|
|
finally:
|
2016-10-29 21:51:17 +00:00
|
|
|
setup_progress.remove(domain)
|
2016-10-29 19:54:47 +00:00
|
|
|
if did_lock:
|
2016-10-29 21:51:17 +00:00
|
|
|
setup_lock.release()
|
2015-01-09 08:07:58 +00:00
|
|
|
|
|
|
|
|
2016-09-04 15:15:52 +00:00
|
|
|
def prepare_setup_component(hass: core.HomeAssistant, config: dict,
|
|
|
|
domain: str):
|
|
|
|
"""Prepare setup of a component and return processed config."""
|
2016-10-27 07:16:23 +00:00
|
|
|
return run_coroutine_threadsafe(
|
|
|
|
async_prepare_setup_component(hass, config, domain), loop=hass.loop
|
|
|
|
).result()
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_prepare_setup_component(hass: core.HomeAssistant, config: dict,
|
|
|
|
domain: str):
|
|
|
|
"""Prepare setup of a component and return processed config.
|
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
2016-09-04 15:15:52 +00:00
|
|
|
# pylint: disable=too-many-return-statements
|
|
|
|
component = loader.get_component(domain)
|
|
|
|
missing_deps = [dep for dep in getattr(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 None
|
|
|
|
|
|
|
|
if hasattr(component, 'CONFIG_SCHEMA'):
|
|
|
|
try:
|
|
|
|
config = component.CONFIG_SCHEMA(config)
|
2016-09-23 07:12:11 +00:00
|
|
|
except vol.Invalid as ex:
|
2016-10-27 07:16:23 +00:00
|
|
|
async_log_exception(ex, domain, config, hass)
|
2016-09-04 15:15:52 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
elif hasattr(component, 'PLATFORM_SCHEMA'):
|
|
|
|
platforms = []
|
|
|
|
for p_name, p_config in config_per_platform(config, domain):
|
|
|
|
# Validate component specific platform schema
|
|
|
|
try:
|
|
|
|
p_validated = component.PLATFORM_SCHEMA(p_config)
|
2016-09-23 07:12:11 +00:00
|
|
|
except vol.Invalid as ex:
|
2016-10-27 07:16:23 +00:00
|
|
|
async_log_exception(ex, domain, config, hass)
|
2016-10-08 18:27:35 +00:00
|
|
|
continue
|
2016-09-04 15:15:52 +00:00
|
|
|
|
|
|
|
# Not all platform components follow same pattern for platforms
|
|
|
|
# So if p_name is None we are not going to validate platform
|
|
|
|
# (the automation component is one of them)
|
|
|
|
if p_name is None:
|
|
|
|
platforms.append(p_validated)
|
|
|
|
continue
|
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
platform = yield from async_prepare_setup_platform(
|
|
|
|
hass, config, domain, p_name)
|
2016-09-04 15:15:52 +00:00
|
|
|
|
|
|
|
if platform is None:
|
2016-10-08 18:27:35 +00:00
|
|
|
continue
|
2016-09-04 15:15:52 +00:00
|
|
|
|
|
|
|
# Validate platform specific schema
|
|
|
|
if hasattr(platform, 'PLATFORM_SCHEMA'):
|
|
|
|
try:
|
2016-10-27 07:16:23 +00:00
|
|
|
# pylint: disable=no-member
|
2016-09-04 15:15:52 +00:00
|
|
|
p_validated = platform.PLATFORM_SCHEMA(p_validated)
|
2016-09-23 07:12:11 +00:00
|
|
|
except vol.Invalid as ex:
|
2016-10-27 07:16:23 +00:00
|
|
|
async_log_exception(ex, '{}.{}'.format(domain, p_name),
|
|
|
|
p_validated, hass)
|
2016-10-08 18:27:35 +00:00
|
|
|
continue
|
2016-09-04 15:15:52 +00:00
|
|
|
|
|
|
|
platforms.append(p_validated)
|
|
|
|
|
|
|
|
# Create a copy of the configuration with all config for current
|
|
|
|
# component removed and add validated config back in.
|
|
|
|
filter_keys = extract_domain_configs(config, domain)
|
|
|
|
config = {key: value for key, value in config.items()
|
|
|
|
if key not in filter_keys}
|
|
|
|
config[domain] = platforms
|
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
res = yield from hass.loop.run_in_executor(
|
|
|
|
None, _handle_requirements, hass, component, domain)
|
|
|
|
if not res:
|
2016-09-04 15:15:52 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
2016-07-28 03:33:49 +00:00
|
|
|
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
|
|
|
|
platform_name: str) -> Optional[ModuleType]:
|
2016-03-07 23:06:04 +00:00
|
|
|
"""Load a platform and makes sure dependencies are setup."""
|
2016-10-27 07:16:23 +00:00
|
|
|
return run_coroutine_threadsafe(
|
|
|
|
async_prepare_setup_platform(hass, config, domain, platform_name),
|
|
|
|
loop=hass.loop
|
|
|
|
).result()
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
|
|
|
|
platform_name: str) \
|
|
|
|
-> Optional[ModuleType]:
|
|
|
|
"""Load a platform and makes sure dependencies are setup.
|
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
|
|
|
if not loader.PREPARED:
|
|
|
|
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
|
2015-05-12 05:23:20 +00:00
|
|
|
|
|
|
|
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
|
|
|
|
|
2016-04-04 19:18:58 +00:00
|
|
|
platform = loader.get_platform(domain, platform_name)
|
2015-05-12 05:23:20 +00:00
|
|
|
|
|
|
|
# Not found
|
|
|
|
if platform is None:
|
2015-09-10 06:37:15 +00:00
|
|
|
_LOGGER.error('Unable to find platform %s', platform_path)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, platform_path)
|
2015-05-12 05:23:20 +00:00
|
|
|
return None
|
|
|
|
|
2015-07-07 07:00:21 +00:00
|
|
|
# Already loaded
|
|
|
|
elif platform_path in hass.config.components:
|
2015-05-12 05:23:20 +00:00
|
|
|
return platform
|
|
|
|
|
|
|
|
# Load dependencies
|
2015-11-26 21:11:59 +00:00
|
|
|
for component in getattr(platform, 'DEPENDENCIES', []):
|
2016-10-27 07:16:23 +00:00
|
|
|
res = yield from async_setup_component(hass, component, config)
|
|
|
|
if not res:
|
2015-11-26 21:11:59 +00:00
|
|
|
_LOGGER.error(
|
|
|
|
'Unable to prepare setup for platform %s because '
|
|
|
|
'dependency %s could not be initialized', platform_path,
|
|
|
|
component)
|
2016-11-03 02:31:09 +00:00
|
|
|
_async_persistent_notification(hass, platform_path, True)
|
2015-11-26 21:11:59 +00:00
|
|
|
return None
|
2015-07-07 07:00:21 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
res = yield from hass.loop.run_in_executor(
|
|
|
|
None, _handle_requirements, hass, platform, platform_path)
|
|
|
|
if not res:
|
2015-07-07 07:00:21 +00:00
|
|
|
return None
|
2015-05-12 05:23:20 +00:00
|
|
|
|
|
|
|
return platform
|
|
|
|
|
|
|
|
|
2016-07-21 05:38:52 +00:00
|
|
|
def from_config_dict(config: Dict[str, Any],
|
2016-08-09 03:42:25 +00:00
|
|
|
hass: Optional[core.HomeAssistant]=None,
|
2016-07-21 05:38:52 +00:00
|
|
|
config_dir: Optional[str]=None,
|
|
|
|
enable_log: bool=True,
|
|
|
|
verbose: bool=False,
|
|
|
|
skip_pip: bool=False,
|
|
|
|
log_rotate_days: Any=None) \
|
|
|
|
-> Optional[core.HomeAssistant]:
|
2016-03-07 23:06:04 +00:00
|
|
|
"""Try to configure Home Assistant from a config dict.
|
2014-04-24 07:40:45 +00:00
|
|
|
|
2014-08-13 12:28:45 +00:00
|
|
|
Dynamically loads required components and its dependencies.
|
|
|
|
"""
|
|
|
|
if hass is None:
|
2015-08-17 03:44:46 +00:00
|
|
|
hass = core.HomeAssistant()
|
2015-08-30 01:11:24 +00:00
|
|
|
if config_dir is not None:
|
2015-08-30 02:19:52 +00:00
|
|
|
config_dir = os.path.abspath(config_dir)
|
|
|
|
hass.config.config_dir = config_dir
|
2016-08-10 06:54:34 +00:00
|
|
|
mount_local_lib_path(config_dir)
|
2014-04-24 07:40:45 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def _async_init_from_config_dict(future):
|
|
|
|
try:
|
|
|
|
re_hass = yield from async_from_config_dict(
|
|
|
|
config, hass, config_dir, enable_log, verbose, skip_pip,
|
|
|
|
log_rotate_days)
|
|
|
|
future.set_result(re_hass)
|
|
|
|
# pylint: disable=broad-except
|
|
|
|
except Exception as exc:
|
|
|
|
future.set_exception(exc)
|
|
|
|
|
|
|
|
# run task
|
2016-11-03 10:07:47 +00:00
|
|
|
future = asyncio.Future(loop=hass.loop)
|
2016-11-19 16:18:33 +00:00
|
|
|
hass.async_add_job(_async_init_from_config_dict(future))
|
2016-10-27 07:16:23 +00:00
|
|
|
hass.loop.run_until_complete(future)
|
|
|
|
|
|
|
|
return future.result()
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_from_config_dict(config: Dict[str, Any],
|
|
|
|
hass: core.HomeAssistant,
|
|
|
|
config_dir: Optional[str]=None,
|
|
|
|
enable_log: bool=True,
|
|
|
|
verbose: bool=False,
|
|
|
|
skip_pip: bool=False,
|
|
|
|
log_rotate_days: Any=None) \
|
|
|
|
-> Optional[core.HomeAssistant]:
|
|
|
|
"""Try to configure Home Assistant from a config dict.
|
|
|
|
|
|
|
|
Dynamically loads required components and its dependencies.
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
2016-11-30 21:02:45 +00:00
|
|
|
hass.async_track_tasks()
|
2016-11-19 16:18:33 +00:00
|
|
|
setup_lock = hass.data.get('setup_lock')
|
|
|
|
if setup_lock is None:
|
|
|
|
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
|
|
|
|
|
|
|
|
yield from setup_lock.acquire()
|
|
|
|
|
2016-05-08 05:24:04 +00:00
|
|
|
core_config = config.get(core.DOMAIN, {})
|
|
|
|
|
2016-03-28 01:48:51 +00:00
|
|
|
try:
|
2016-10-27 07:16:23 +00:00
|
|
|
yield from conf_util.async_process_ha_core_config(hass, core_config)
|
2016-06-27 16:02:45 +00:00
|
|
|
except vol.Invalid as ex:
|
2016-10-27 07:16:23 +00:00
|
|
|
async_log_exception(ex, 'homeassistant', core_config, hass)
|
2016-03-28 01:48:51 +00:00
|
|
|
return None
|
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
yield from hass.loop.run_in_executor(
|
|
|
|
None, conf_util.process_ha_config_upgrade, hass)
|
2015-03-19 06:02:58 +00:00
|
|
|
|
2015-08-30 06:02:07 +00:00
|
|
|
if enable_log:
|
2017-02-13 05:24:07 +00:00
|
|
|
async_enable_logging(hass, verbose, log_rotate_days)
|
2015-01-18 06:23:07 +00:00
|
|
|
|
2015-09-04 21:50:57 +00:00
|
|
|
hass.config.skip_pip = skip_pip
|
|
|
|
if skip_pip:
|
|
|
|
_LOGGER.warning('Skipping pip installation of required modules. '
|
|
|
|
'This may cause issues.')
|
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
if not loader.PREPARED:
|
|
|
|
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
|
2014-11-28 23:34:42 +00:00
|
|
|
|
2017-01-14 06:01:47 +00:00
|
|
|
# Merge packages
|
|
|
|
conf_util.merge_packages_config(
|
|
|
|
config, core_config.get(conf_util.CONF_PACKAGES, {}))
|
|
|
|
|
2014-08-13 12:28:45 +00:00
|
|
|
# Make a copy because we are mutating it.
|
2016-11-19 16:18:33 +00:00
|
|
|
# Use OrderedDict in case original one was one.
|
2015-02-28 19:17:50 +00:00
|
|
|
# Convert values to dictionaries if they are None
|
2016-11-19 16:18:33 +00:00
|
|
|
new_config = OrderedDict()
|
|
|
|
for key, value in config.items():
|
|
|
|
new_config[key] = value or {}
|
|
|
|
config = new_config
|
2013-10-22 05:06:22 +00:00
|
|
|
|
2014-12-07 07:57:02 +00:00
|
|
|
# Filter out the repeating and common config section [homeassistant]
|
2015-09-29 06:09:05 +00:00
|
|
|
components = set(key.split(' ')[0] for key in config.keys()
|
|
|
|
if key != core.DOMAIN)
|
2014-10-22 15:12:32 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
# setup components
|
|
|
|
# pylint: disable=not-an-iterable
|
|
|
|
res = yield from core_components.async_setup(hass, config)
|
|
|
|
if not res:
|
|
|
|
_LOGGER.error('Home Assistant core failed to initialize. '
|
|
|
|
'Further initialization aborted.')
|
|
|
|
return hass
|
2016-06-25 23:40:33 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
yield from persistent_notification.async_setup(hass, config)
|
2014-12-17 05:46:02 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
_LOGGER.info('Home Assistant core initialized')
|
2016-01-24 22:46:05 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
# Give event decorators access to HASS
|
|
|
|
event_decorators.HASS = hass
|
|
|
|
service.HASS = hass
|
2013-10-13 17:42:22 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
# Setup the components
|
|
|
|
for domain in loader.load_order_components(components):
|
|
|
|
yield from _async_setup_component(hass, domain, config)
|
2016-10-13 16:09:07 +00:00
|
|
|
|
2016-11-19 16:18:33 +00:00
|
|
|
setup_lock.release()
|
|
|
|
|
2016-11-30 21:02:45 +00:00
|
|
|
yield from hass.async_stop_track_tasks()
|
|
|
|
|
2017-02-09 05:58:45 +00:00
|
|
|
async_register_signal_handling(hass)
|
2014-08-13 12:28:45 +00:00
|
|
|
return hass
|
2013-10-22 05:06:22 +00:00
|
|
|
|
2014-01-24 01:46:29 +00:00
|
|
|
|
2016-07-21 05:38:52 +00:00
|
|
|
def from_config_file(config_path: str,
|
|
|
|
hass: Optional[core.HomeAssistant]=None,
|
|
|
|
verbose: bool=False,
|
|
|
|
skip_pip: bool=True,
|
|
|
|
log_rotate_days: Any=None):
|
2016-03-07 23:06:04 +00:00
|
|
|
"""Read the configuration file and try to start all the functionality.
|
|
|
|
|
|
|
|
Will add functionality to 'hass' parameter if given,
|
2014-08-13 12:28:45 +00:00
|
|
|
instantiates a new Home Assistant object if 'hass' is not given.
|
|
|
|
"""
|
2014-09-21 02:19:39 +00:00
|
|
|
if hass is None:
|
2015-08-17 03:44:46 +00:00
|
|
|
hass = core.HomeAssistant()
|
2014-09-21 02:19:39 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def _async_init_from_config_file(future):
|
|
|
|
try:
|
|
|
|
re_hass = yield from async_from_config_file(
|
|
|
|
config_path, hass, verbose, skip_pip, log_rotate_days)
|
|
|
|
future.set_result(re_hass)
|
|
|
|
# pylint: disable=broad-except
|
|
|
|
except Exception as exc:
|
|
|
|
future.set_exception(exc)
|
|
|
|
|
|
|
|
# run task
|
2016-11-03 10:07:47 +00:00
|
|
|
future = asyncio.Future(loop=hass.loop)
|
|
|
|
hass.loop.create_task(_async_init_from_config_file(future))
|
2016-10-27 07:16:23 +00:00
|
|
|
hass.loop.run_until_complete(future)
|
|
|
|
|
|
|
|
return future.result()
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_from_config_file(config_path: str,
|
|
|
|
hass: core.HomeAssistant,
|
|
|
|
verbose: bool=False,
|
|
|
|
skip_pip: bool=True,
|
|
|
|
log_rotate_days: Any=None):
|
|
|
|
"""Read the configuration file and try to start all the functionality.
|
|
|
|
|
|
|
|
Will add functionality to 'hass' parameter.
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
2015-03-19 06:02:58 +00:00
|
|
|
# Set config dir to directory holding config file
|
2015-08-30 02:19:52 +00:00
|
|
|
config_dir = os.path.abspath(os.path.dirname(config_path))
|
|
|
|
hass.config.config_dir = config_dir
|
2016-10-27 07:16:23 +00:00
|
|
|
yield from hass.loop.run_in_executor(
|
|
|
|
None, mount_local_lib_path, config_dir)
|
2014-09-21 02:19:39 +00:00
|
|
|
|
2017-02-13 05:24:07 +00:00
|
|
|
async_enable_logging(hass, verbose, log_rotate_days)
|
2015-08-30 06:02:07 +00:00
|
|
|
|
2016-04-09 22:25:01 +00:00
|
|
|
try:
|
2016-10-27 07:16:23 +00:00
|
|
|
config_dict = yield from hass.loop.run_in_executor(
|
|
|
|
None, conf_util.load_yaml_config_file, config_path)
|
2016-04-09 22:25:01 +00:00
|
|
|
except HomeAssistantError:
|
|
|
|
return None
|
2016-08-20 19:39:56 +00:00
|
|
|
finally:
|
|
|
|
clear_secret_cache()
|
2013-10-13 17:42:22 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
hass = yield from async_from_config_dict(
|
|
|
|
config_dict, hass, enable_log=False, skip_pip=skip_pip)
|
|
|
|
return hass
|
2015-01-18 06:23:07 +00:00
|
|
|
|
|
|
|
|
2017-02-13 05:24:07 +00:00
|
|
|
@core.callback
|
|
|
|
def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
|
|
|
|
log_rotate_days=None) -> None:
|
2016-10-27 07:16:23 +00:00
|
|
|
"""Setup the logging.
|
|
|
|
|
2017-02-13 05:24:07 +00:00
|
|
|
This method must be run in the event loop.
|
2016-10-27 07:16:23 +00:00
|
|
|
"""
|
2016-05-20 06:20:07 +00:00
|
|
|
logging.basicConfig(level=logging.INFO)
|
2017-01-20 05:31:44 +00:00
|
|
|
fmt = ("%(asctime)s %(levelname)s (%(threadName)s) "
|
|
|
|
"[%(name)s] %(message)s")
|
|
|
|
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
|
|
|
|
datefmt = '%y-%m-%d %H:%M:%S'
|
2016-10-17 19:14:10 +00:00
|
|
|
|
|
|
|
# suppress overly verbose logs from libraries that aren't helpful
|
|
|
|
logging.getLogger("requests").setLevel(logging.WARNING)
|
|
|
|
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
2016-10-24 06:48:01 +00:00
|
|
|
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
2016-10-17 19:14:10 +00:00
|
|
|
|
2016-05-20 06:20:07 +00:00
|
|
|
try:
|
|
|
|
from colorlog import ColoredFormatter
|
|
|
|
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
2017-01-20 05:31:44 +00:00
|
|
|
colorfmt,
|
|
|
|
datefmt=datefmt,
|
2016-05-20 06:20:07 +00:00
|
|
|
reset=True,
|
|
|
|
log_colors={
|
|
|
|
'DEBUG': 'cyan',
|
|
|
|
'INFO': 'green',
|
|
|
|
'WARNING': 'yellow',
|
|
|
|
'ERROR': 'red',
|
|
|
|
'CRITICAL': 'red',
|
|
|
|
}
|
|
|
|
))
|
|
|
|
except ImportError:
|
|
|
|
pass
|
2015-01-18 06:23:07 +00:00
|
|
|
|
|
|
|
# Log errors to a file if we have write access to file or config dir
|
2015-11-07 09:44:02 +00:00
|
|
|
err_log_path = hass.config.path(ERROR_LOG_FILENAME)
|
2015-01-18 06:23:07 +00:00
|
|
|
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 \
|
2015-03-19 06:02:58 +00:00
|
|
|
(not err_path_exists and os.access(hass.config.config_dir, os.W_OK)):
|
2015-01-18 06:23:07 +00:00
|
|
|
|
2015-09-04 22:22:42 +00:00
|
|
|
if log_rotate_days:
|
|
|
|
err_handler = logging.handlers.TimedRotatingFileHandler(
|
|
|
|
err_log_path, when='midnight', backupCount=log_rotate_days)
|
|
|
|
else:
|
|
|
|
err_handler = logging.FileHandler(
|
|
|
|
err_log_path, mode='w', delay=True)
|
2015-01-18 06:23:07 +00:00
|
|
|
|
2015-09-01 06:12:00 +00:00
|
|
|
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
|
2017-01-20 05:31:44 +00:00
|
|
|
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
|
2016-12-16 23:51:06 +00:00
|
|
|
|
|
|
|
async_handler = AsyncHandler(hass.loop, err_handler)
|
2017-02-13 05:24:07 +00:00
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_stop_async_handler(event):
|
|
|
|
"""Cleanup async handler."""
|
|
|
|
logging.getLogger('').removeHandler(async_handler)
|
|
|
|
yield from async_handler.async_close(blocking=True)
|
|
|
|
|
|
|
|
hass.bus.async_listen_once(
|
|
|
|
EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler)
|
2016-12-16 23:51:06 +00:00
|
|
|
|
2015-09-01 06:12:00 +00:00
|
|
|
logger = logging.getLogger('')
|
2016-12-16 23:51:06 +00:00
|
|
|
logger.addHandler(async_handler)
|
2015-12-23 02:39:46 +00:00
|
|
|
logger.setLevel(logging.INFO)
|
2015-01-18 06:23:07 +00:00
|
|
|
|
|
|
|
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 07:56:04 +00:00
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
def log_exception(ex, domain, config, hass):
|
|
|
|
"""Generate log exception for config validation."""
|
|
|
|
run_callback_threadsafe(
|
|
|
|
hass.loop, async_log_exception, ex, domain, config, hass).result()
|
2016-06-27 16:02:45 +00:00
|
|
|
|
|
|
|
|
2016-11-03 02:31:09 +00:00
|
|
|
@core.callback
|
|
|
|
def _async_persistent_notification(hass: core.HomeAssistant, component: str,
|
|
|
|
link: Optional[bool]=False):
|
|
|
|
"""Print a persistent notification.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
|
|
|
_PERSISTENT_ERRORS[component] = _PERSISTENT_ERRORS.get(component) or link
|
|
|
|
_lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name)
|
|
|
|
if link else name for name, link in _PERSISTENT_ERRORS.items()]
|
|
|
|
message = ('The following components and platforms could not be set up:\n'
|
|
|
|
'* ' + '\n* '.join(list(_lst)) + '\nPlease check your config')
|
|
|
|
persistent_notification.async_create(
|
|
|
|
hass, message, 'Invalid config', 'invalid_config')
|
|
|
|
|
|
|
|
|
2016-10-27 07:16:23 +00:00
|
|
|
@core.callback
|
|
|
|
def async_log_exception(ex, domain, config, hass):
|
|
|
|
"""Generate log exception for config validation.
|
|
|
|
|
2016-10-28 19:26:52 +00:00
|
|
|
This method must be run in the event loop.
|
2016-10-27 07:16:23 +00:00
|
|
|
"""
|
2016-08-10 04:19:50 +00:00
|
|
|
message = 'Invalid config for [{}]: '.format(domain)
|
2016-11-03 02:31:09 +00:00
|
|
|
if hass is not None:
|
|
|
|
_async_persistent_notification(hass, domain, True)
|
2016-09-28 06:59:34 +00:00
|
|
|
|
2016-08-10 04:19:50 +00:00
|
|
|
if 'extra keys not allowed' in ex.error_message:
|
|
|
|
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
|
|
|
|
.format(ex.path[-1], domain, domain,
|
2016-10-27 07:16:23 +00:00
|
|
|
'->'.join(str(m) for m in ex.path))
|
2016-08-10 04:19:50 +00:00
|
|
|
else:
|
2016-09-28 06:59:34 +00:00
|
|
|
message += '{}.'.format(humanize_error(config, ex))
|
2016-08-10 04:19:50 +00:00
|
|
|
|
2016-10-25 18:13:32 +00:00
|
|
|
domain_config = config.get(domain, config)
|
2017-01-14 05:13:17 +00:00
|
|
|
message += " (See {}, line {}). ".format(
|
2016-10-25 18:13:32 +00:00
|
|
|
getattr(domain_config, '__config_file__', '?'),
|
|
|
|
getattr(domain_config, '__line__', '?'))
|
2016-09-28 06:59:34 +00:00
|
|
|
|
|
|
|
if domain != 'homeassistant':
|
2016-10-27 07:16:23 +00:00
|
|
|
message += ('Please check the docs at '
|
2016-09-28 06:59:34 +00:00
|
|
|
'https://home-assistant.io/components/{}/'.format(domain))
|
2016-08-10 04:19:50 +00:00
|
|
|
|
|
|
|
_LOGGER.error(message)
|
2016-08-10 06:54:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def mount_local_lib_path(config_dir: str) -> str:
|
2016-10-27 07:16:23 +00:00
|
|
|
"""Add local library to Python Path.
|
|
|
|
|
|
|
|
Async friendly.
|
|
|
|
"""
|
2016-08-10 06:54:34 +00:00
|
|
|
deps_dir = os.path.join(config_dir, 'deps')
|
|
|
|
if deps_dir not in sys.path:
|
|
|
|
sys.path.insert(0, os.path.join(config_dir, 'deps'))
|
|
|
|
return deps_dir
|