Components+configuration now loaded dynamically
A major change to the bootstrapping of Home Assistant decoupling the knowledge in bootstrap for a more dynamic approach. This refactoring also prepares the code for different configuration backends and the loading components from different places.
This commit is contained in:
parent
cb33b3bf24
commit
997c2e8ef6
19 changed files with 473 additions and 309 deletions
|
@ -1,20 +1,15 @@
|
||||||
[common]
|
[homeassistant]
|
||||||
latitude=32.87336
|
latitude=32.87336
|
||||||
longitude=-117.22743
|
longitude=-117.22743
|
||||||
|
|
||||||
[http]
|
[http]
|
||||||
api_password=mypass
|
api_password=mypass
|
||||||
|
|
||||||
[light.hue]
|
[light]
|
||||||
host=192.168.1.2
|
type=hue
|
||||||
|
|
||||||
[device_tracker.tomato]
|
[device_tracker]
|
||||||
host=192.168.1.1
|
type=netgear
|
||||||
username=admin
|
|
||||||
password=PASSWORD
|
|
||||||
http_id=aaaaaaaaaaaaaaa
|
|
||||||
|
|
||||||
[device_tracker.netgear]
|
|
||||||
host=192.168.1.1
|
host=192.168.1.1
|
||||||
username=admin
|
username=admin
|
||||||
password=PASSWORD
|
password=PASSWORD
|
||||||
|
@ -38,9 +33,9 @@ download_dir=downloads
|
||||||
|
|
||||||
# A comma seperated list of states that have to be tracked as a single group
|
# A comma seperated list of states that have to be tracked as a single group
|
||||||
# Grouped states should share the same states (ON/OFF or HOME/NOT_HOME)
|
# Grouped states should share the same states (ON/OFF or HOME/NOT_HOME)
|
||||||
[group]
|
# [group]
|
||||||
living_room=light.Bowl,light.Ceiling,light.TV_back_light
|
# living_room=light.Bowl,light.Ceiling,light.TV_back_light
|
||||||
bedroom=light.Bed_light
|
# bedroom=light.Bed_light
|
||||||
|
|
||||||
[process]
|
[process]
|
||||||
# items are which processes to look for: <entity_id>=<search string within ps>
|
# items are which processes to look for: <entity_id>=<search string within ps>
|
||||||
|
|
|
@ -30,6 +30,14 @@ ATTR_NOW = "now"
|
||||||
ATTR_DOMAIN = "domain"
|
ATTR_DOMAIN = "domain"
|
||||||
ATTR_SERVICE = "service"
|
ATTR_SERVICE = "service"
|
||||||
|
|
||||||
|
CONF_LATITUDE = "latitude"
|
||||||
|
CONF_LONGITUDE = "longitude"
|
||||||
|
CONF_TYPE = "type"
|
||||||
|
CONF_HOST = "host"
|
||||||
|
CONF_HOSTS = "hosts"
|
||||||
|
CONF_USERNAME = "username"
|
||||||
|
CONF_PASSWORD = "password"
|
||||||
|
|
||||||
# How often time_changed event should fire
|
# How often time_changed event should fire
|
||||||
TIMER_INTERVAL = 10 # seconds
|
TIMER_INTERVAL = 10 # seconds
|
||||||
|
|
||||||
|
@ -334,7 +342,7 @@ class EventBus(object):
|
||||||
|
|
||||||
for func in listeners:
|
for func in listeners:
|
||||||
self._pool.add_job(JobPriority.from_event_type(event_type),
|
self._pool.add_job(JobPriority.from_event_type(event_type),
|
||||||
(func, event))
|
(func, event))
|
||||||
|
|
||||||
def listen(self, event_type, listener):
|
def listen(self, event_type, listener):
|
||||||
""" Listen for all events or events of a specific type.
|
""" Listen for all events or events of a specific type.
|
||||||
|
@ -553,8 +561,8 @@ class ServiceRegistry(object):
|
||||||
service_call = ServiceCall(domain, service, service_data)
|
service_call = ServiceCall(domain, service, service_data)
|
||||||
|
|
||||||
self._pool.add_job(JobPriority.EVENT_SERVICE,
|
self._pool.add_job(JobPriority.EVENT_SERVICE,
|
||||||
(self._services[domain][service],
|
(self._services[domain][service],
|
||||||
service_call))
|
service_call))
|
||||||
|
|
||||||
|
|
||||||
class Timer(threading.Thread):
|
class Timer(threading.Thread):
|
||||||
|
|
|
@ -7,20 +7,146 @@ After bootstrapping you can add your own components or
|
||||||
start by calling homeassistant.start_home_assistant(bus)
|
start by calling homeassistant.start_home_assistant(bus)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import importlib
|
|
||||||
import configparser
|
import configparser
|
||||||
import logging
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
import homeassistant
|
import homeassistant
|
||||||
import homeassistant.components as components
|
import homeassistant.components as core_components
|
||||||
|
import homeassistant.components.group as group
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches,too-many-locals,too-many-statements
|
# pylint: disable=too-many-branches
|
||||||
def from_config_file(config_path, enable_logging=True):
|
def from_config_dict(config, hass=None):
|
||||||
""" Starts home assistant with all possible functionality
|
"""
|
||||||
based on a config file.
|
Tries to configure Home Assistant from a config dict.
|
||||||
Will return a tuple (bus, statemachine). """
|
|
||||||
|
|
||||||
|
Dynamically loads required components and its dependencies.
|
||||||
|
"""
|
||||||
|
if hass is None:
|
||||||
|
hass = homeassistant.HomeAssistant()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Make a copy because we are mutating it.
|
||||||
|
# Convert it to defaultdict so components can always have config dict
|
||||||
|
config = defaultdict(dict, config)
|
||||||
|
|
||||||
|
# List of loaded components
|
||||||
|
components = {}
|
||||||
|
|
||||||
|
# List of components to validate
|
||||||
|
to_validate = []
|
||||||
|
|
||||||
|
# List of validated components
|
||||||
|
validated = []
|
||||||
|
|
||||||
|
# List of components we are going to load
|
||||||
|
to_load = [key for key in config.keys() if key != homeassistant.DOMAIN]
|
||||||
|
|
||||||
|
# Load required components
|
||||||
|
while to_load:
|
||||||
|
domain = to_load.pop()
|
||||||
|
|
||||||
|
component = core_components.get_component(domain, logger)
|
||||||
|
|
||||||
|
# if None it does not exist, error already thrown by get_component
|
||||||
|
if component is not None:
|
||||||
|
components[domain] = component
|
||||||
|
|
||||||
|
# Special treatment for GROUP, we want to load it as late as
|
||||||
|
# possible. We do this by loading it if all other to be loaded
|
||||||
|
# modules depend on it.
|
||||||
|
if component.DOMAIN == group.DOMAIN:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Components with no dependencies are valid
|
||||||
|
elif not component.DEPENDENCIES:
|
||||||
|
validated.append(domain)
|
||||||
|
|
||||||
|
# If dependencies we'll validate it later
|
||||||
|
else:
|
||||||
|
to_validate.append(domain)
|
||||||
|
|
||||||
|
# Make sure to load all dependencies that are not being loaded
|
||||||
|
for dependency in component.DEPENDENCIES:
|
||||||
|
if dependency not in chain(components.keys(), to_load):
|
||||||
|
to_load.append(dependency)
|
||||||
|
|
||||||
|
# Validate dependencies
|
||||||
|
group_added = False
|
||||||
|
|
||||||
|
while to_validate:
|
||||||
|
newly_validated = []
|
||||||
|
|
||||||
|
for domain in to_validate:
|
||||||
|
if all(domain in validated for domain
|
||||||
|
in components[domain].DEPENDENCIES):
|
||||||
|
|
||||||
|
newly_validated.append(domain)
|
||||||
|
|
||||||
|
# We validated new domains this iteration, add them to validated
|
||||||
|
if newly_validated:
|
||||||
|
|
||||||
|
# Add newly validated domains to validated
|
||||||
|
validated.extend(newly_validated)
|
||||||
|
|
||||||
|
# remove domains from to_validate
|
||||||
|
for domain in newly_validated:
|
||||||
|
to_validate.remove(domain)
|
||||||
|
|
||||||
|
newly_validated.clear()
|
||||||
|
|
||||||
|
# Nothing validated this iteration. Add group dependency and try again.
|
||||||
|
elif not group_added:
|
||||||
|
group_added = True
|
||||||
|
validated.append(group.DOMAIN)
|
||||||
|
|
||||||
|
# Group has already been added and we still can't validate all.
|
||||||
|
# Report missing deps as error and skip loading of these domains
|
||||||
|
else:
|
||||||
|
for domain in to_validate:
|
||||||
|
missing_deps = [dep for dep in components[domain].DEPENDENCIES
|
||||||
|
if dep not in validated]
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
"Could not validate all dependencies for {}: {}".format(
|
||||||
|
domain, ", ".join(missing_deps)))
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# Setup the components
|
||||||
|
if core_components.setup(hass, config):
|
||||||
|
logger.info("Home Assistant core initialized")
|
||||||
|
|
||||||
|
for domain in validated:
|
||||||
|
component = components[domain]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if component.setup(hass, config):
|
||||||
|
logger.info("component {} initialized".format(domain))
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
"component {} failed to initialize".format(domain))
|
||||||
|
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
logger.exception(
|
||||||
|
"Error during setup of component {}".format(domain))
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.error(("Home Assistant core failed to initialize. "
|
||||||
|
"Further initialization aborted."))
|
||||||
|
|
||||||
|
return hass
|
||||||
|
|
||||||
|
|
||||||
|
def from_config_file(config_path, hass=None, enable_logging=True):
|
||||||
|
"""
|
||||||
|
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 enable_logging:
|
if enable_logging:
|
||||||
# Setup the logging for home assistant.
|
# Setup the logging for home assistant.
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
@ -34,196 +160,16 @@ def from_config_file(config_path, enable_logging=True):
|
||||||
datefmt='%H:%M %d-%m-%y'))
|
datefmt='%H:%M %d-%m-%y'))
|
||||||
logging.getLogger('').addHandler(err_handler)
|
logging.getLogger('').addHandler(err_handler)
|
||||||
|
|
||||||
# Start the actual bootstrapping
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
statusses = []
|
|
||||||
|
|
||||||
# Read config
|
# Read config
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(config_path)
|
config.read(config_path)
|
||||||
|
|
||||||
# Init core
|
config_dict = {}
|
||||||
hass = homeassistant.HomeAssistant()
|
|
||||||
|
|
||||||
has_opt = config.has_option
|
for section in config.sections():
|
||||||
get_opt = config.get
|
config_dict[section] = {}
|
||||||
has_section = config.has_section
|
|
||||||
add_status = lambda name, result: statusses.append((name, result))
|
|
||||||
load_module = lambda module: importlib.import_module(
|
|
||||||
'homeassistant.components.'+module)
|
|
||||||
|
|
||||||
def get_opt_safe(section, option, default=None):
|
for key, val in config.items(section):
|
||||||
""" Failure proof option retriever. """
|
config_dict[section][key] = val
|
||||||
try:
|
|
||||||
return config.get(section, option)
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
return default
|
|
||||||
|
|
||||||
def get_hosts(section):
|
return from_config_dict(config_dict, hass)
|
||||||
""" Helper method to retrieve hosts from config. """
|
|
||||||
if has_opt(section, "hosts"):
|
|
||||||
return get_opt(section, "hosts").split(",")
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Device scanner
|
|
||||||
dev_scan = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# For the error message if not all option fields exist
|
|
||||||
opt_fields = "host, username, password"
|
|
||||||
|
|
||||||
if has_section('device_tracker.tomato'):
|
|
||||||
device_tracker = load_module('device_tracker')
|
|
||||||
|
|
||||||
dev_scan_name = "Tomato"
|
|
||||||
opt_fields += ", http_id"
|
|
||||||
|
|
||||||
dev_scan = device_tracker.TomatoDeviceScanner(
|
|
||||||
get_opt('device_tracker.tomato', 'host'),
|
|
||||||
get_opt('device_tracker.tomato', 'username'),
|
|
||||||
get_opt('device_tracker.tomato', 'password'),
|
|
||||||
get_opt('device_tracker.tomato', 'http_id'))
|
|
||||||
|
|
||||||
elif has_section('device_tracker.netgear'):
|
|
||||||
device_tracker = load_module('device_tracker')
|
|
||||||
|
|
||||||
dev_scan_name = "Netgear"
|
|
||||||
|
|
||||||
dev_scan = device_tracker.NetgearDeviceScanner(
|
|
||||||
get_opt('device_tracker.netgear', 'host'),
|
|
||||||
get_opt('device_tracker.netgear', 'username'),
|
|
||||||
get_opt('device_tracker.netgear', 'password'))
|
|
||||||
|
|
||||||
elif has_section('device_tracker.luci'):
|
|
||||||
device_tracker = load_module('device_tracker')
|
|
||||||
|
|
||||||
dev_scan_name = "Luci"
|
|
||||||
|
|
||||||
dev_scan = device_tracker.LuciDeviceScanner(
|
|
||||||
get_opt('device_tracker.luci', 'host'),
|
|
||||||
get_opt('device_tracker.luci', 'username'),
|
|
||||||
get_opt('device_tracker.luci', 'password'))
|
|
||||||
|
|
||||||
except configparser.NoOptionError:
|
|
||||||
# If one of the options didn't exist
|
|
||||||
logger.exception(("Error initializing {}DeviceScanner, "
|
|
||||||
"could not find one of the following config "
|
|
||||||
"options: {}".format(dev_scan_name, opt_fields)))
|
|
||||||
|
|
||||||
add_status("Device Scanner - {}".format(dev_scan_name), False)
|
|
||||||
|
|
||||||
if dev_scan:
|
|
||||||
add_status("Device Scanner - {}".format(dev_scan_name),
|
|
||||||
dev_scan.success_init)
|
|
||||||
|
|
||||||
if not dev_scan.success_init:
|
|
||||||
dev_scan = None
|
|
||||||
|
|
||||||
# Device Tracker
|
|
||||||
if dev_scan:
|
|
||||||
device_tracker.DeviceTracker(hass, dev_scan)
|
|
||||||
|
|
||||||
add_status("Device Tracker", True)
|
|
||||||
|
|
||||||
# Sun tracker
|
|
||||||
if has_opt("common", "latitude") and \
|
|
||||||
has_opt("common", "longitude"):
|
|
||||||
|
|
||||||
sun = load_module('sun')
|
|
||||||
|
|
||||||
add_status("Sun",
|
|
||||||
sun.setup(hass,
|
|
||||||
get_opt("common", "latitude"),
|
|
||||||
get_opt("common", "longitude")))
|
|
||||||
else:
|
|
||||||
sun = None
|
|
||||||
|
|
||||||
# Chromecast
|
|
||||||
if has_section("chromecast"):
|
|
||||||
chromecast = load_module('chromecast')
|
|
||||||
|
|
||||||
hosts = get_hosts("chromecast")
|
|
||||||
|
|
||||||
add_status("Chromecast", chromecast.setup(hass, hosts))
|
|
||||||
|
|
||||||
# WeMo
|
|
||||||
if has_section("wemo"):
|
|
||||||
wemo = load_module('wemo')
|
|
||||||
|
|
||||||
hosts = get_hosts("wemo")
|
|
||||||
|
|
||||||
add_status("WeMo", wemo.setup(hass, hosts))
|
|
||||||
|
|
||||||
# Process tracking
|
|
||||||
if has_section("process"):
|
|
||||||
process = load_module('process')
|
|
||||||
|
|
||||||
processes = dict(config.items('process'))
|
|
||||||
add_status("process", process.setup(hass, processes))
|
|
||||||
|
|
||||||
# Light control
|
|
||||||
if has_section("light.hue"):
|
|
||||||
light = load_module('light')
|
|
||||||
|
|
||||||
light_control = light.HueLightControl(get_opt_safe("hue", "host"))
|
|
||||||
|
|
||||||
add_status("Light - Hue", light_control.success_init)
|
|
||||||
|
|
||||||
if light_control.success_init:
|
|
||||||
light.setup(hass, light_control)
|
|
||||||
else:
|
|
||||||
light_control = None
|
|
||||||
|
|
||||||
else:
|
|
||||||
light_control = None
|
|
||||||
|
|
||||||
if has_opt("downloader", "download_dir"):
|
|
||||||
downloader = load_module('downloader')
|
|
||||||
|
|
||||||
add_status("Downloader", downloader.setup(
|
|
||||||
hass, get_opt("downloader", "download_dir")))
|
|
||||||
|
|
||||||
add_status("Core components", components.setup(hass))
|
|
||||||
|
|
||||||
if has_section('browser'):
|
|
||||||
add_status("Browser", load_module('browser').setup(hass))
|
|
||||||
|
|
||||||
if has_section('keyboard'):
|
|
||||||
add_status("Keyboard", load_module('keyboard').setup(hass))
|
|
||||||
|
|
||||||
# Init HTTP interface
|
|
||||||
if has_opt("http", "api_password"):
|
|
||||||
http = load_module('http')
|
|
||||||
|
|
||||||
http.setup(hass, get_opt("http", "api_password"))
|
|
||||||
|
|
||||||
add_status("HTTP", True)
|
|
||||||
|
|
||||||
# Init groups
|
|
||||||
if has_section("group"):
|
|
||||||
group = load_module('group')
|
|
||||||
|
|
||||||
for name, entity_ids in config.items("group"):
|
|
||||||
add_status("Group - {}".format(name),
|
|
||||||
group.setup(hass, name, entity_ids.split(",")))
|
|
||||||
|
|
||||||
# Light trigger
|
|
||||||
if light_control and sun:
|
|
||||||
device_sun_light_trigger = load_module('device_sun_light_trigger')
|
|
||||||
|
|
||||||
light_group = get_opt_safe("device_sun_light_trigger", "light_group")
|
|
||||||
light_profile = get_opt_safe("device_sun_light_trigger",
|
|
||||||
"light_profile")
|
|
||||||
|
|
||||||
add_status("Device Sun Light Trigger",
|
|
||||||
device_sun_light_trigger.setup(hass,
|
|
||||||
light_group, light_profile))
|
|
||||||
|
|
||||||
for component, success_init in statusses:
|
|
||||||
status = "initialized" if success_init else "Failed to initialize"
|
|
||||||
|
|
||||||
logger.info("{}: {}".format(component, status))
|
|
||||||
|
|
||||||
return hass
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ Each component should publish services only under its own domain.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import itertools as it
|
import itertools as it
|
||||||
|
import logging
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
|
@ -44,23 +45,51 @@ SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
|
||||||
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
|
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
|
||||||
|
|
||||||
|
|
||||||
def _get_component(component):
|
def get_component(component, logger=None):
|
||||||
""" Returns requested component. """
|
""" Tries to load specified component.
|
||||||
|
Only returns it if also found to be valid."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return importlib.import_module(
|
comp = importlib.import_module(
|
||||||
'homeassistant.components.{}'.format(component))
|
'homeassistant.components.{}'.format(component))
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# If we got a bogus component the input will fail
|
if logger:
|
||||||
|
logger.error(
|
||||||
|
"Failed to find component {}".format(component))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Validation if component has required methods and attributes
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if not hasattr(comp, 'DOMAIN'):
|
||||||
|
errors.append("Missing DOMAIN attribute")
|
||||||
|
|
||||||
|
if not hasattr(comp, 'DEPENDENCIES'):
|
||||||
|
errors.append("Missing DEPENDENCIES attribute")
|
||||||
|
|
||||||
|
if not hasattr(comp, 'setup'):
|
||||||
|
errors.append("Missing setup method")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
if logger:
|
||||||
|
logger.error("Found invalid component {}: {}".format(
|
||||||
|
component, ", ".join(errors)))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
return comp
|
||||||
|
|
||||||
|
|
||||||
def is_on(hass, entity_id=None):
|
def is_on(hass, entity_id=None):
|
||||||
""" Loads up the module to call the is_on method.
|
""" Loads up the module to call the is_on method.
|
||||||
If there is no entity id given we will check all. """
|
If there is no entity id given we will check all. """
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if entity_id:
|
if entity_id:
|
||||||
group = _get_component('group')
|
group = get_component('group', logger)
|
||||||
|
|
||||||
entity_ids = group.expand_entity_ids([entity_id])
|
entity_ids = group.expand_entity_ids([entity_id])
|
||||||
else:
|
else:
|
||||||
|
@ -69,7 +98,7 @@ def is_on(hass, entity_id=None):
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
domain = util.split_entity_id(entity_id)[0]
|
domain = util.split_entity_id(entity_id)[0]
|
||||||
|
|
||||||
module = _get_component(domain)
|
module = get_component(domain, logger)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if module.is_on(hass, entity_id):
|
if module.is_on(hass, entity_id):
|
||||||
|
@ -100,7 +129,7 @@ def extract_entity_ids(hass, service):
|
||||||
entity_ids = []
|
entity_ids = []
|
||||||
|
|
||||||
if service.data and ATTR_ENTITY_ID in service.data:
|
if service.data and ATTR_ENTITY_ID in service.data:
|
||||||
group = _get_component('group')
|
group = get_component('group')
|
||||||
|
|
||||||
# Entity ID attr can be a list or a string
|
# Entity ID attr can be a list or a string
|
||||||
service_ent_id = service.data[ATTR_ENTITY_ID]
|
service_ent_id = service.data[ATTR_ENTITY_ID]
|
||||||
|
@ -117,7 +146,8 @@ def extract_entity_ids(hass, service):
|
||||||
return entity_ids
|
return entity_ids
|
||||||
|
|
||||||
|
|
||||||
def setup(hass):
|
# pylint: disable=unused-argument
|
||||||
|
def setup(hass, config):
|
||||||
""" Setup general services related to homeassistant. """
|
""" Setup general services related to homeassistant. """
|
||||||
|
|
||||||
def handle_turn_service(service):
|
def handle_turn_service(service):
|
||||||
|
|
|
@ -6,11 +6,13 @@ Provides functionality to launch a webbrowser on the host machine.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DOMAIN = "browser"
|
DOMAIN = "browser"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
SERVICE_BROWSE_URL = "browse_url"
|
SERVICE_BROWSE_URL = "browse_url"
|
||||||
|
|
||||||
|
|
||||||
def setup(hass):
|
# pylint: disable=unused-argument
|
||||||
|
def setup(hass, config):
|
||||||
""" Listen for browse_url events and open
|
""" Listen for browse_url events and open
|
||||||
the url in the default webbrowser. """
|
the url in the default webbrowser. """
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import homeassistant.util as util
|
||||||
import homeassistant.components as components
|
import homeassistant.components as components
|
||||||
|
|
||||||
DOMAIN = 'chromecast'
|
DOMAIN = 'chromecast'
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ def media_prev_track(hass, entity_id=None):
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-locals, too-many-branches
|
# pylint: disable=too-many-locals, too-many-branches
|
||||||
def setup(hass, hosts=None):
|
def setup(hass, config):
|
||||||
""" Listen for chromecast events. """
|
""" Listen for chromecast events. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -113,8 +114,11 @@ def setup(hass, hosts=None):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if 'hosts' in config[DOMAIN]:
|
||||||
|
hosts = config[DOMAIN]['hosts'].split(",")
|
||||||
|
|
||||||
# If no hosts given, scan for chromecasts
|
# If no hosts given, scan for chromecasts
|
||||||
if not hosts:
|
else:
|
||||||
logger.info("Scanning for Chromecasts")
|
logger.info("Scanning for Chromecasts")
|
||||||
hosts = pychromecast.discover_chromecasts()
|
hosts = pychromecast.discover_chromecasts()
|
||||||
|
|
||||||
|
@ -131,7 +135,7 @@ def setup(hass, hosts=None):
|
||||||
|
|
||||||
casts[entity_id] = cast
|
casts[entity_id] = cast
|
||||||
|
|
||||||
except pychromecast.ConnectionError:
|
except pychromecast.ChromecastConnectionError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not casts:
|
if not casts:
|
||||||
|
|
|
@ -11,19 +11,26 @@ from datetime import datetime, timedelta
|
||||||
import homeassistant.components as components
|
import homeassistant.components as components
|
||||||
from . import light, sun, device_tracker, group
|
from . import light, sun, device_tracker, group
|
||||||
|
|
||||||
|
DOMAIN = "device_sun_light_trigger"
|
||||||
|
DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun']
|
||||||
|
|
||||||
LIGHT_TRANSITION_TIME = timedelta(minutes=15)
|
LIGHT_TRANSITION_TIME = timedelta(minutes=15)
|
||||||
|
|
||||||
# Light profile to be used if none given
|
# Light profile to be used if none given
|
||||||
LIGHT_PROFILE = 'relax'
|
LIGHT_PROFILE = 'relax'
|
||||||
|
|
||||||
|
CONF_LIGHT_PROFILE = 'light_profile'
|
||||||
|
CONF_LIGHT_GROUP = 'light_group'
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
def setup(hass, light_group=None, light_profile=None):
|
def setup(hass, config):
|
||||||
""" Triggers to turn lights on or off based on device precense. """
|
""" Triggers to turn lights on or off based on device precense. """
|
||||||
|
|
||||||
light_group = light_group or light.GROUP_NAME_ALL_LIGHTS
|
light_group = config[DOMAIN].get(CONF_LIGHT_GROUP,
|
||||||
light_profile = light_profile or LIGHT_PROFILE
|
light.GROUP_NAME_ALL_LIGHTS)
|
||||||
|
|
||||||
|
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE, LIGHT_PROFILE)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -61,8 +68,7 @@ def setup(hass, light_group=None, light_profile=None):
|
||||||
def turn_light_on_before_sunset(light_id):
|
def turn_light_on_before_sunset(light_id):
|
||||||
""" Helper function to turn on lights slowly if there
|
""" Helper function to turn on lights slowly if there
|
||||||
are devices home and the light is not on yet. """
|
are devices home and the light is not on yet. """
|
||||||
if (device_tracker.is_on(hass) and
|
if device_tracker.is_on(hass) and not light.is_on(hass, light_id):
|
||||||
not light.is_on(hass, light_id)):
|
|
||||||
|
|
||||||
light.turn_on(hass, light_id,
|
light.turn_on(hass, light_id,
|
||||||
transition=LIGHT_TRANSITION_TIME.seconds,
|
transition=LIGHT_TRANSITION_TIME.seconds,
|
||||||
|
@ -99,8 +105,8 @@ def setup(hass, light_group=None, light_profile=None):
|
||||||
light_needed = not (lights_are_on or sun.is_on(hass))
|
light_needed = not (lights_are_on or sun.is_on(hass))
|
||||||
|
|
||||||
# Specific device came home ?
|
# Specific device came home ?
|
||||||
if (entity != device_tracker.ENTITY_ID_ALL_DEVICES and
|
if entity != device_tracker.ENTITY_ID_ALL_DEVICES and \
|
||||||
new_state.state == components.STATE_HOME):
|
new_state.state == components.STATE_HOME:
|
||||||
|
|
||||||
# These variables are needed for the elif check
|
# These variables are needed for the elif check
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
|
@ -14,12 +14,14 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.components as components
|
import homeassistant.components as components
|
||||||
|
|
||||||
from homeassistant.components import group
|
from homeassistant.components import group
|
||||||
|
|
||||||
DOMAIN = "device_tracker"
|
DOMAIN = "device_tracker"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
|
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
|
||||||
|
|
||||||
|
@ -39,6 +41,8 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
# Filename to save known devices to
|
# Filename to save known devices to
|
||||||
KNOWN_DEVICES_FILE = "known_devices.csv"
|
KNOWN_DEVICES_FILE = "known_devices.csv"
|
||||||
|
|
||||||
|
CONF_HTTP_ID = "http_id"
|
||||||
|
|
||||||
|
|
||||||
def is_on(hass, entity_id=None):
|
def is_on(hass, entity_id=None):
|
||||||
""" Returns if any or specified device is home. """
|
""" Returns if any or specified device is home. """
|
||||||
|
@ -47,16 +51,69 @@ def is_on(hass, entity_id=None):
|
||||||
return hass.states.is_state(entity, components.STATE_HOME)
|
return hass.states.is_state(entity, components.STATE_HOME)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Sets up the device tracker. """
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# We have flexible requirements for device tracker so
|
||||||
|
# we cannot use util.validate_config
|
||||||
|
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
|
||||||
|
if not ha.CONF_TYPE in conf:
|
||||||
|
logger.error(
|
||||||
|
'Missing required configuration item in {}: {}'.format(
|
||||||
|
DOMAIN, ha.CONF_TYPE))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
fields = [ha.CONF_HOST, ha.CONF_USERNAME, ha.CONF_PASSWORD]
|
||||||
|
|
||||||
|
router_type = conf[ha.CONF_TYPE]
|
||||||
|
|
||||||
|
if router_type == 'tomato':
|
||||||
|
fields.append(CONF_HTTP_ID)
|
||||||
|
|
||||||
|
scanner = TomatoDeviceScanner
|
||||||
|
|
||||||
|
elif router_type == 'netgear':
|
||||||
|
scanner = NetgearDeviceScanner
|
||||||
|
|
||||||
|
elif router_type == 'luci':
|
||||||
|
scanner = LuciDeviceScanner
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.error('Found unknown router type {}'.format(router_type))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not util.validate_config(config, {DOMAIN: fields}, logger):
|
||||||
|
return False
|
||||||
|
|
||||||
|
device_scanner = scanner(conf)
|
||||||
|
|
||||||
|
if not device_scanner.success_init:
|
||||||
|
logger.error(
|
||||||
|
"Failed to initialize device scanner for {}".format(router_type))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
DeviceTracker(hass, device_scanner)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
class DeviceTracker(object):
|
class DeviceTracker(object):
|
||||||
""" Class that tracks which devices are home and which are not. """
|
""" Class that tracks which devices are home and which are not. """
|
||||||
|
|
||||||
def __init__(self, hass, device_scanner, error_scanning=None):
|
def __init__(self, hass, device_scanner):
|
||||||
self.states = hass.states
|
self.states = hass.states
|
||||||
|
|
||||||
self.device_scanner = device_scanner
|
self.device_scanner = device_scanner
|
||||||
|
|
||||||
self.error_scanning = error_scanning or TIME_SPAN_FOR_ERROR_IN_SCANNING
|
self.error_scanning = TIME_SPAN_FOR_ERROR_IN_SCANNING
|
||||||
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -84,7 +141,7 @@ class DeviceTracker(object):
|
||||||
|
|
||||||
self.update_devices()
|
self.update_devices()
|
||||||
|
|
||||||
group.setup(hass, GROUP_NAME_ALL_DEVICES, self.device_entity_ids)
|
group.setup_group(hass, GROUP_NAME_ALL_DEVICES, self.device_entity_ids)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_entity_ids(self):
|
def device_entity_ids(self):
|
||||||
|
@ -164,8 +221,8 @@ class DeviceTracker(object):
|
||||||
except IOError:
|
except IOError:
|
||||||
self.logger.exception((
|
self.logger.exception((
|
||||||
"DeviceTracker:Error updating {}"
|
"DeviceTracker:Error updating {}"
|
||||||
"with {} new devices").format(
|
"with {} new devices").format(KNOWN_DEVICES_FILE,
|
||||||
KNOWN_DEVICES_FILE, len(unknown_devices)))
|
len(unknown_devices)))
|
||||||
|
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
|
@ -223,8 +280,8 @@ class DeviceTracker(object):
|
||||||
|
|
||||||
# Remove entities that are no longer maintained
|
# Remove entities that are no longer maintained
|
||||||
new_entity_ids = set([known_devices[device]['entity_id']
|
new_entity_ids = set([known_devices[device]['entity_id']
|
||||||
for device in known_devices
|
for device in known_devices
|
||||||
if known_devices[device]['track']])
|
if known_devices[device]['track']])
|
||||||
|
|
||||||
for entity_id in \
|
for entity_id in \
|
||||||
self.device_entity_ids - new_entity_ids:
|
self.device_entity_ids - new_entity_ids:
|
||||||
|
@ -246,8 +303,8 @@ class DeviceTracker(object):
|
||||||
self.invalid_known_devices_file = True
|
self.invalid_known_devices_file = True
|
||||||
self.logger.warning((
|
self.logger.warning((
|
||||||
"Invalid {} found. "
|
"Invalid {} found. "
|
||||||
"We won't update it with new found devices.").
|
"We won't update it with new found devices."
|
||||||
format(KNOWN_DEVICES_FILE))
|
).format(KNOWN_DEVICES_FILE))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
@ -261,7 +318,10 @@ class TomatoDeviceScanner(object):
|
||||||
http://paulusschoutsen.nl/blog/2013/10/tomato-api-documentation/
|
http://paulusschoutsen.nl/blog/2013/10/tomato-api-documentation/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host, username, password, http_id):
|
def __init__(self, config):
|
||||||
|
host, http_id = config['host'], config['http_id']
|
||||||
|
username, password = config['username'], config['password']
|
||||||
|
|
||||||
self.req = requests.Request('POST',
|
self.req = requests.Request('POST',
|
||||||
'http://{}/update.cgi'.format(host),
|
'http://{}/update.cgi'.format(host),
|
||||||
data={'_http_id': http_id,
|
data={'_http_id': http_id,
|
||||||
|
@ -309,8 +369,8 @@ class TomatoDeviceScanner(object):
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
|
|
||||||
# if date_updated is None or the date is too old we scan for new data
|
# if date_updated is None or the date is too old we scan for new data
|
||||||
if (not self.date_updated or datetime.now() - self.date_updated >
|
if not self.date_updated or \
|
||||||
MIN_TIME_BETWEEN_SCANS):
|
datetime.now() - self.date_updated > MIN_TIME_BETWEEN_SCANS:
|
||||||
|
|
||||||
self.logger.info("Tomato:Scanning")
|
self.logger.info("Tomato:Scanning")
|
||||||
|
|
||||||
|
@ -380,7 +440,10 @@ class TomatoDeviceScanner(object):
|
||||||
class NetgearDeviceScanner(object):
|
class NetgearDeviceScanner(object):
|
||||||
""" This class queries a Netgear wireless router using the SOAP-api. """
|
""" This class queries a Netgear wireless router using the SOAP-api. """
|
||||||
|
|
||||||
def __init__(self, host, username, password):
|
def __init__(self, config):
|
||||||
|
host = config['host']
|
||||||
|
username, password = config['username'], config['password']
|
||||||
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.date_updated = None
|
self.date_updated = None
|
||||||
self.last_results = []
|
self.last_results = []
|
||||||
|
@ -442,8 +505,8 @@ class NetgearDeviceScanner(object):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# if date_updated is None or the date is too old we scan for
|
# if date_updated is None or the date is too old we scan for
|
||||||
# new data
|
# new data
|
||||||
if (not self.date_updated or datetime.now() - self.date_updated >
|
if not self.date_updated or \
|
||||||
MIN_TIME_BETWEEN_SCANS):
|
datetime.now() - self.date_updated > MIN_TIME_BETWEEN_SCANS:
|
||||||
|
|
||||||
self.logger.info("Netgear:Scanning")
|
self.logger.info("Netgear:Scanning")
|
||||||
|
|
||||||
|
@ -470,7 +533,10 @@ class LuciDeviceScanner(object):
|
||||||
(Currently, we do only wifi iwscan, and no DHCP lease access.)
|
(Currently, we do only wifi iwscan, and no DHCP lease access.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host, username, password):
|
def __init__(self, config):
|
||||||
|
host = config['host']
|
||||||
|
username, password = config['username'], config['password']
|
||||||
|
|
||||||
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
|
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
|
||||||
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
@ -554,8 +620,8 @@ class LuciDeviceScanner(object):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# if date_updated is None or the date is too old we scan
|
# if date_updated is None or the date is too old we scan
|
||||||
# for new data
|
# for new data
|
||||||
if (not self.date_updated or datetime.now() - self.date_updated >
|
if not self.date_updated or \
|
||||||
MIN_TIME_BETWEEN_SCANS):
|
datetime.now() - self.date_updated > MIN_TIME_BETWEEN_SCANS:
|
||||||
|
|
||||||
self.logger.info("Checking ARP")
|
self.logger.info("Checking ARP")
|
||||||
|
|
||||||
|
|
|
@ -12,15 +12,18 @@ import threading
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
|
||||||
DOMAIN = "downloader"
|
DOMAIN = "downloader"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
SERVICE_DOWNLOAD_FILE = "download_file"
|
SERVICE_DOWNLOAD_FILE = "download_file"
|
||||||
|
|
||||||
ATTR_URL = "url"
|
ATTR_URL = "url"
|
||||||
ATTR_SUBDIR = "subdir"
|
ATTR_SUBDIR = "subdir"
|
||||||
|
|
||||||
|
CONF_DOWNLOAD_DIR = 'download_dir'
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
def setup(hass, download_path):
|
def setup(hass, config):
|
||||||
""" Listens for download events to download files. """
|
""" Listens for download events to download files. """
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -33,6 +36,11 @@ def setup(hass, download_path):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not util.validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
|
||||||
|
return False
|
||||||
|
|
||||||
|
download_path = config[DOMAIN][CONF_DOWNLOAD_DIR]
|
||||||
|
|
||||||
if not os.path.isdir(download_path):
|
if not os.path.isdir(download_path):
|
||||||
|
|
||||||
logger.error(
|
logger.error(
|
||||||
|
@ -106,8 +114,7 @@ def setup(hass, download_path):
|
||||||
|
|
||||||
final_path = "{}_{}.{}".format(path, tries, ext)
|
final_path = "{}_{}.{}".format(path, tries, ext)
|
||||||
|
|
||||||
logger.info("{} -> {}".format(
|
logger.info("{} -> {}".format(url, final_path))
|
||||||
url, final_path))
|
|
||||||
|
|
||||||
with open(final_path, 'wb') as fil:
|
with open(final_path, 'wb') as fil:
|
||||||
for chunk in req.iter_content(1024):
|
for chunk in req.iter_content(1024):
|
||||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.components import (STATE_ON, STATE_OFF,
|
||||||
ATTR_ENTITY_ID)
|
ATTR_ENTITY_ID)
|
||||||
|
|
||||||
DOMAIN = "group"
|
DOMAIN = "group"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||||
|
|
||||||
|
@ -89,13 +90,21 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-locals
|
# pylint: disable=too-many-branches, too-many-locals
|
||||||
def setup(hass, name, entity_ids):
|
def setup(hass, config):
|
||||||
|
""" Sets up all groups found definded in the configuration. """
|
||||||
|
|
||||||
|
for name, entity_ids in config[DOMAIN].items():
|
||||||
|
entity_ids = entity_ids.split(",")
|
||||||
|
|
||||||
|
setup_group(hass, name, entity_ids)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def setup_group(hass, name, entity_ids):
|
||||||
""" Sets up a group state that is the combined state of
|
""" Sets up a group state that is the combined state of
|
||||||
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
||||||
|
|
||||||
# Convert entity_ids to a list incase it is an iterable
|
|
||||||
entity_ids = list(entity_ids)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Loop over the given entities to:
|
# Loop over the given entities to:
|
||||||
|
@ -118,9 +127,11 @@ def setup(hass, name, entity_ids):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# We did not find a matching group_type
|
# We did not find a matching group_type
|
||||||
errors.append("Found unexpected state '{}'".format(
|
errors.append(
|
||||||
name, state.state))
|
"Entity {} has ungroupable state '{}'".format(
|
||||||
|
name, state.state))
|
||||||
|
|
||||||
|
# Stop check all other entity IDs and report as error
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check if entity exists
|
# Check if entity exists
|
||||||
|
@ -134,43 +145,48 @@ def setup(hass, name, entity_ids):
|
||||||
entity_id, state.state, group_off, group_on))
|
entity_id, state.state, group_off, group_on))
|
||||||
|
|
||||||
# Keep track of the group state to init later on
|
# Keep track of the group state to init later on
|
||||||
elif group_state == group_off and state.state == group_on:
|
elif state.state == group_on:
|
||||||
group_state = group_on
|
group_state = group_on
|
||||||
|
|
||||||
|
if group_type is None and not errors:
|
||||||
|
errors.append('Unable to determine group type for {}'.format(name))
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
logger.error("Error setting up state group {}: {}".format(
|
logger.error("Error setting up group {}: {}".format(
|
||||||
name, ", ".join(errors)))
|
name, ", ".join(errors)))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
group_entity_id = ENTITY_ID_FORMAT.format(name)
|
else:
|
||||||
state_attr = {ATTR_ENTITY_ID: entity_ids}
|
group_entity_id = ENTITY_ID_FORMAT.format(name)
|
||||||
|
state_attr = {ATTR_ENTITY_ID: entity_ids}
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def update_group_state(entity_id, old_state, new_state):
|
def update_group_state(entity_id, old_state, new_state):
|
||||||
""" Updates the group state based on a state change by a tracked
|
""" Updates the group state based on a state change by
|
||||||
entity. """
|
a tracked entity. """
|
||||||
|
|
||||||
cur_group_state = hass.states.get(group_entity_id).state
|
cur_gr_state = hass.states.get(group_entity_id).state
|
||||||
|
|
||||||
# if cur_group_state = OFF and new_state = ON: set ON
|
# if cur_gr_state = OFF and new_state = ON: set ON
|
||||||
# if cur_group_state = ON and new_state = OFF: research
|
# if cur_gr_state = ON and new_state = OFF: research
|
||||||
# else: ignore
|
# else: ignore
|
||||||
|
|
||||||
if cur_group_state == group_off and new_state.state == group_on:
|
if cur_gr_state == group_off and new_state.state == group_on:
|
||||||
|
|
||||||
hass.states.set(group_entity_id, group_on, state_attr)
|
hass.states.set(group_entity_id, group_on, state_attr)
|
||||||
|
|
||||||
elif cur_group_state == group_on and new_state.state == group_off:
|
elif cur_gr_state == group_on and new_state.state == group_off:
|
||||||
|
|
||||||
# Check if any of the other states is still on
|
# Check if any of the other states is still on
|
||||||
if not any([hass.states.is_state(ent_id, group_on)
|
if not any([hass.states.is_state(ent_id, group_on)
|
||||||
for ent_id in entity_ids if entity_id != ent_id]):
|
for ent_id in entity_ids
|
||||||
hass.states.set(group_entity_id, group_off, state_attr)
|
if entity_id != ent_id]):
|
||||||
|
hass.states.set(group_entity_id, group_off, state_attr)
|
||||||
|
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
hass.track_state_change(entity_id, update_group_state)
|
hass.track_state_change(entity_id, update_group_state)
|
||||||
|
|
||||||
hass.states.set(group_entity_id, group_state, state_attr)
|
hass.states.set(group_entity_id, group_state, state_attr)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -89,6 +89,8 @@ import homeassistant.remote as rem
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.components import (STATE_ON, STATE_OFF,
|
from homeassistant.components import (STATE_ON, STATE_OFF,
|
||||||
SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
||||||
|
DOMAIN = "http"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
HTTP_OK = 200
|
HTTP_OK = 200
|
||||||
HTTP_CREATED = 201
|
HTTP_CREATED = 201
|
||||||
|
@ -120,6 +122,10 @@ DOMAIN_ICONS = {
|
||||||
"downloader": "glyphicon-download-alt"
|
"downloader": "glyphicon-download-alt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CONF_API_PASSWORD = "api_password"
|
||||||
|
CONF_SERVER_HOST = "server_host"
|
||||||
|
CONF_SERVER_PORT = "server_port"
|
||||||
|
|
||||||
|
|
||||||
def _get_domain_icon(domain):
|
def _get_domain_icon(domain):
|
||||||
""" Returns HTML that shows domain icon. """
|
""" Returns HTML that shows domain icon. """
|
||||||
|
@ -127,12 +133,19 @@ def _get_domain_icon(domain):
|
||||||
DOMAIN_ICONS.get(domain, ""))
|
DOMAIN_ICONS.get(domain, ""))
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, api_password, server_port=None, server_host=None):
|
def setup(hass, config):
|
||||||
""" Sets up the HTTP API and debug interface. """
|
""" Sets up the HTTP API and debug interface. """
|
||||||
server_port = server_port or rem.SERVER_PORT
|
|
||||||
|
if not util.validate_config(config, {DOMAIN: [CONF_API_PASSWORD]},
|
||||||
|
logging.getLogger(__name__)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
api_password = config[DOMAIN]['api_password']
|
||||||
|
|
||||||
# If no server host is given, accept all incoming requests
|
# If no server host is given, accept all incoming requests
|
||||||
server_host = server_host or '0.0.0.0'
|
server_host = config[DOMAIN].get(CONF_SERVER_HOST, '0.0.0.0')
|
||||||
|
|
||||||
|
server_port = config[DOMAIN].get(CONF_SERVER_PORT, rem.SERVER_PORT)
|
||||||
|
|
||||||
server = HomeAssistantHTTPServer((server_host, server_port),
|
server = HomeAssistantHTTPServer((server_host, server_port),
|
||||||
RequestHandler, hass, api_password)
|
RequestHandler, hass, api_password)
|
||||||
|
@ -147,6 +160,8 @@ def setup(hass, api_password, server_port=None, server_host=None):
|
||||||
hass.local_api = \
|
hass.local_api = \
|
||||||
rem.API(util.get_local_ip(), api_password, server_port)
|
rem.API(util.get_local_ip(), api_password, server_port)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
""" Handle HTTP requests in a threaded fashion. """
|
""" Handle HTTP requests in a threaded fashion. """
|
||||||
|
@ -213,7 +228,7 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||||
# /event_forwarding
|
# /event_forwarding
|
||||||
('POST', rem.URL_API_EVENT_FORWARD, '_handle_post_api_event_forward'),
|
('POST', rem.URL_API_EVENT_FORWARD, '_handle_post_api_event_forward'),
|
||||||
('DELETE', rem.URL_API_EVENT_FORWARD,
|
('DELETE', rem.URL_API_EVENT_FORWARD,
|
||||||
'_handle_delete_api_event_forward'),
|
'_handle_delete_api_event_forward'),
|
||||||
|
|
||||||
# Statis files
|
# Statis files
|
||||||
('GET', re.compile(r'/static/(?P<file>[a-zA-Z\._\-0-9/]+)'),
|
('GET', re.compile(r'/static/(?P<file>[a-zA-Z\._\-0-9/]+)'),
|
||||||
|
@ -407,7 +422,7 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||||
"href='#'></a>").format(action, state.entity_id))
|
"href='#'></a>").format(action, state.entity_id))
|
||||||
|
|
||||||
write("</td><td>{}</td><td>{}</td></tr>".format(
|
write("</td><td>{}</td><td>{}</td></tr>".format(
|
||||||
attributes, util.datetime_to_str(state.last_changed)))
|
attributes, util.datetime_to_str(state.last_changed)))
|
||||||
|
|
||||||
# Change state form
|
# Change state form
|
||||||
write(("<tr><td></td><td><input name='entity_id' class='form-control' "
|
write(("<tr><td></td><td><input name='entity_id' class='form-control' "
|
||||||
|
|
|
@ -9,6 +9,7 @@ import logging
|
||||||
import homeassistant.components as components
|
import homeassistant.components as components
|
||||||
|
|
||||||
DOMAIN = "keyboard"
|
DOMAIN = "keyboard"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
|
||||||
def volume_up(hass):
|
def volume_up(hass):
|
||||||
|
@ -41,13 +42,14 @@ def media_prev_track(hass):
|
||||||
hass.call_service(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK)
|
hass.call_service(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass):
|
# pylint: disable=unused-argument
|
||||||
|
def setup(hass, config):
|
||||||
""" Listen for keyboard events. """
|
""" Listen for keyboard events. """
|
||||||
try:
|
try:
|
||||||
import pykeyboard
|
import pykeyboard
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logging.getLogger(__name__).exception(
|
logging.getLogger(__name__).exception(
|
||||||
"MediaButtons: Error while importing dependency PyUserInput.")
|
"Error while importing dependency PyUserInput.")
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ from collections import namedtuple
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.components import (group, extract_entity_ids,
|
from homeassistant.components import (group, extract_entity_ids,
|
||||||
STATE_ON, STATE_OFF,
|
STATE_ON, STATE_OFF,
|
||||||
|
@ -63,6 +64,7 @@ from homeassistant.components import (group, extract_entity_ids,
|
||||||
|
|
||||||
|
|
||||||
DOMAIN = "light"
|
DOMAIN = "light"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
GROUP_NAME_ALL_LIGHTS = 'all_lights'
|
GROUP_NAME_ALL_LIGHTS = 'all_lights'
|
||||||
ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format(
|
ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format(
|
||||||
|
@ -136,11 +138,26 @@ def turn_off(hass, entity_id=None, transition=None):
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-locals
|
# pylint: disable=too-many-branches, too-many-locals
|
||||||
def setup(hass, light_control):
|
def setup(hass, config):
|
||||||
""" Exposes light control via statemachine and services. """
|
""" Exposes light control via statemachine and services. """
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, logger):
|
||||||
|
return False
|
||||||
|
|
||||||
|
light_type = config[DOMAIN][ha.CONF_TYPE]
|
||||||
|
|
||||||
|
if light_type == 'hue':
|
||||||
|
light_init = HueLightControl
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.error("Found unknown light type: {}".format(light_type))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
light_control = light_init(config[DOMAIN])
|
||||||
|
|
||||||
ent_to_light = {}
|
ent_to_light = {}
|
||||||
light_to_ent = {}
|
light_to_ent = {}
|
||||||
|
|
||||||
|
@ -188,9 +205,9 @@ def setup(hass, light_control):
|
||||||
""" Update the state of all the lights. """
|
""" Update the state of all the lights. """
|
||||||
|
|
||||||
# First time this method gets called, force_reload should be True
|
# First time this method gets called, force_reload should be True
|
||||||
if (force_reload or
|
if force_reload or \
|
||||||
datetime.now() - update_lights_state.last_updated >
|
datetime.now() - update_lights_state.last_updated > \
|
||||||
MIN_TIME_BETWEEN_SCANS):
|
MIN_TIME_BETWEEN_SCANS:
|
||||||
|
|
||||||
logger.info("Updating light status")
|
logger.info("Updating light status")
|
||||||
update_lights_state.last_updated = datetime.now()
|
update_lights_state.last_updated = datetime.now()
|
||||||
|
@ -206,7 +223,7 @@ def setup(hass, light_control):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Track all lights in a group
|
# Track all lights in a group
|
||||||
group.setup(hass, GROUP_NAME_ALL_LIGHTS, light_to_ent.values())
|
group.setup_group(hass, GROUP_NAME_ALL_LIGHTS, light_to_ent.values())
|
||||||
|
|
||||||
# Load built-in profiles and custom profiles
|
# Load built-in profiles and custom profiles
|
||||||
profile_paths = [os.path.dirname(__file__), os.getcwd()]
|
profile_paths = [os.path.dirname(__file__), os.getcwd()]
|
||||||
|
@ -336,9 +353,11 @@ def _hue_to_light_state(info):
|
||||||
class HueLightControl(object):
|
class HueLightControl(object):
|
||||||
""" Class to interface with the Hue light system. """
|
""" Class to interface with the Hue light system. """
|
||||||
|
|
||||||
def __init__(self, host=None):
|
def __init__(self, config):
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
host = config.get(ha.CONF_HOST, None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import phue
|
import phue
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
|
@ -26,12 +26,13 @@ from homeassistant.components import STATE_ON, STATE_OFF
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
|
||||||
DOMAIN = 'process'
|
DOMAIN = 'process'
|
||||||
|
DEPENDENCIES = []
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
PS_STRING = 'ps awx'
|
PS_STRING = 'ps awx'
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, processes):
|
def setup(hass, config):
|
||||||
""" Sets up a check if specified processes are running.
|
""" Sets up a check if specified processes are running.
|
||||||
|
|
||||||
processes: dict mapping entity id to substring to search for
|
processes: dict mapping entity id to substring to search for
|
||||||
|
@ -39,7 +40,7 @@ def setup(hass, processes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring
|
entities = {ENTITY_ID_FORMAT.format(util.slugify(pname)): pstring
|
||||||
for pname, pstring in processes.items()}
|
for pname, pstring in config[DOMAIN].items()}
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def update_process_states(time):
|
def update_process_states(time):
|
||||||
|
|
|
@ -7,8 +7,11 @@ Provides functionality to keep track of the sun.
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
|
||||||
|
DEPENDENCIES = []
|
||||||
|
DOMAIN = "sun"
|
||||||
ENTITY_ID = "sun.sun"
|
ENTITY_ID = "sun.sun"
|
||||||
|
|
||||||
STATE_ABOVE_HORIZON = "above_horizon"
|
STATE_ABOVE_HORIZON = "above_horizon"
|
||||||
|
@ -49,10 +52,16 @@ def next_rising(hass):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, latitude, longitude):
|
def setup(hass, config):
|
||||||
""" Tracks the state of the sun. """
|
""" Tracks the state of the sun. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if not util.validate_config(config,
|
||||||
|
{ha.DOMAIN: [ha.CONF_LATITUDE,
|
||||||
|
ha.CONF_LONGITUDE]},
|
||||||
|
logger):
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ephem
|
import ephem
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -61,12 +70,15 @@ def setup(hass, latitude, longitude):
|
||||||
|
|
||||||
sun = ephem.Sun() # pylint: disable=no-member
|
sun = ephem.Sun() # pylint: disable=no-member
|
||||||
|
|
||||||
|
latitude = config[ha.DOMAIN][ha.CONF_LATITUDE]
|
||||||
|
longitude = config[ha.DOMAIN][ha.CONF_LONGITUDE]
|
||||||
|
|
||||||
def update_sun_state(now): # pylint: disable=unused-argument
|
def update_sun_state(now): # pylint: disable=unused-argument
|
||||||
""" Method to update the current state of the sun and
|
""" Method to update the current state of the sun and
|
||||||
set time of next setting and rising. """
|
set time of next setting and rising. """
|
||||||
observer = ephem.Observer()
|
observer = ephem.Observer()
|
||||||
observer.lat = latitude
|
observer.lat = latitude # pylint: disable=assigning-non-slot
|
||||||
observer.long = longitude
|
observer.long = longitude # pylint: disable=assigning-non-slot
|
||||||
|
|
||||||
next_rising_dt = ephem.localtime(observer.next_rising(sun))
|
next_rising_dt = ephem.localtime(observer.next_rising(sun))
|
||||||
next_setting_dt = ephem.localtime(observer.next_setting(sun))
|
next_setting_dt = ephem.localtime(observer.next_setting(sun))
|
||||||
|
|
|
@ -4,12 +4,14 @@ Component to interface with WeMo devices on the network.
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import homeassistant as ha
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.components import (group, extract_entity_ids,
|
from homeassistant.components import (group, extract_entity_ids,
|
||||||
STATE_ON, STATE_OFF,
|
STATE_ON, STATE_OFF,
|
||||||
SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||||
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
|
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
|
||||||
DOMAIN = 'wemo'
|
DOMAIN = 'wemo'
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
GROUP_NAME_ALL_WEMOS = 'all_wemos'
|
GROUP_NAME_ALL_WEMOS = 'all_wemos'
|
||||||
ENTITY_ID_ALL_WEMOS = group.ENTITY_ID_FORMAT.format(
|
ENTITY_ID_ALL_WEMOS = group.ENTITY_ID_FORMAT.format(
|
||||||
|
@ -47,7 +49,7 @@ def turn_off(hass, entity_id=None):
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
def setup(hass, hosts=None):
|
def setup(hass, config):
|
||||||
""" Track states and offer events for WeMo switches. """
|
""" Track states and offer events for WeMo switches. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -61,10 +63,10 @@ def setup(hass, hosts=None):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if hosts:
|
if ha.CONF_HOSTS in config[DOMAIN]:
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
for host in hosts:
|
for host in config[DOMAIN][ha.CONF_HOSTS].split(","):
|
||||||
device = pywemo.device_from_host(host)
|
device = pywemo.device_from_host(host)
|
||||||
|
|
||||||
if device:
|
if device:
|
||||||
|
@ -125,9 +127,9 @@ def setup(hass, hosts=None):
|
||||||
""" Update states of all WeMo devices. """
|
""" Update states of all WeMo devices. """
|
||||||
|
|
||||||
# First time this method gets called, force_reload should be True
|
# First time this method gets called, force_reload should be True
|
||||||
if (force_reload or
|
if force_reload or \
|
||||||
datetime.now() - update_wemos_state.last_updated >
|
datetime.now() - update_wemos_state.last_updated > \
|
||||||
MIN_TIME_BETWEEN_SCANS):
|
MIN_TIME_BETWEEN_SCANS:
|
||||||
|
|
||||||
logger.info("Updating WeMo status")
|
logger.info("Updating WeMo status")
|
||||||
update_wemos_state.last_updated = datetime.now()
|
update_wemos_state.last_updated = datetime.now()
|
||||||
|
@ -138,7 +140,7 @@ def setup(hass, hosts=None):
|
||||||
update_wemos_state(None, True)
|
update_wemos_state(None, True)
|
||||||
|
|
||||||
# Track all lights in a group
|
# Track all lights in a group
|
||||||
group.setup(hass, GROUP_NAME_ALL_WEMOS, sno_to_ent.values())
|
group.setup_group(hass, GROUP_NAME_ALL_WEMOS, sno_to_ent.values())
|
||||||
|
|
||||||
def handle_wemo_service(service):
|
def handle_wemo_service(service):
|
||||||
""" Handles calls to the WeMo service. """
|
""" Handles calls to the WeMo service. """
|
||||||
|
|
|
@ -95,7 +95,8 @@ class HomeAssistant(ha.HomeAssistant):
|
||||||
def __init__(self, remote_api, local_api=None):
|
def __init__(self, remote_api, local_api=None):
|
||||||
if not remote_api.validate_api():
|
if not remote_api.validate_api():
|
||||||
raise ha.HomeAssistantError(
|
raise ha.HomeAssistantError(
|
||||||
"Remote API not valid: {}".format(remote_api.status))
|
"Remote API at {}:{} not valid: {}".format(
|
||||||
|
remote_api.host, remote_api.port, remote_api.status))
|
||||||
|
|
||||||
self.remote_api = remote_api
|
self.remote_api = remote_api
|
||||||
self.local_api = local_api
|
self.local_api = local_api
|
||||||
|
@ -113,7 +114,10 @@ class HomeAssistant(ha.HomeAssistant):
|
||||||
import homeassistant.components.http as http
|
import homeassistant.components.http as http
|
||||||
import random
|
import random
|
||||||
|
|
||||||
http.setup(self, '%030x'.format(random.randrange(16**30)))
|
# pylint: disable=too-many-format-args
|
||||||
|
random_password = '%030x'.format(random.randrange(16**30))
|
||||||
|
|
||||||
|
http.setup(self, random_password)
|
||||||
|
|
||||||
ha.Timer(self)
|
ha.Timer(self)
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,8 @@ def ensure_homeassistant_started():
|
||||||
hass.bus.listen('test_event', len)
|
hass.bus.listen('test_event', len)
|
||||||
hass.states.set('test', 'a_state')
|
hass.states.set('test', 'a_state')
|
||||||
|
|
||||||
http.setup(hass, API_PASSWORD)
|
http.setup(hass,
|
||||||
|
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD}})
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
|
|
||||||
|
@ -55,12 +56,16 @@ def ensure_homeassistant_started():
|
||||||
def ensure_slave_started():
|
def ensure_slave_started():
|
||||||
""" Ensure a home assistant slave is started. """
|
""" Ensure a home assistant slave is started. """
|
||||||
|
|
||||||
|
ensure_homeassistant_started()
|
||||||
|
|
||||||
if not HAHelper.slave:
|
if not HAHelper.slave:
|
||||||
local_api = remote.API("127.0.0.1", API_PASSWORD, 8124)
|
local_api = remote.API("127.0.0.1", API_PASSWORD, 8124)
|
||||||
remote_api = remote.API("127.0.0.1", API_PASSWORD)
|
remote_api = remote.API("127.0.0.1", API_PASSWORD)
|
||||||
slave = remote.HomeAssistant(local_api, remote_api)
|
slave = remote.HomeAssistant(remote_api, local_api)
|
||||||
|
|
||||||
http.setup(slave, API_PASSWORD, 8124)
|
http.setup(slave,
|
||||||
|
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
|
||||||
|
http.CONF_SERVER_PORT: 8124}})
|
||||||
|
|
||||||
slave.start()
|
slave.start()
|
||||||
|
|
||||||
|
@ -73,7 +78,7 @@ def ensure_slave_started():
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
class TestHTTPInterface(unittest.TestCase):
|
class TestHTTP(unittest.TestCase):
|
||||||
""" Test the HTTP debug interface and API. """
|
""" Test the HTTP debug interface and API. """
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -167,6 +167,30 @@ class OrderedEnum(enum.Enum):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
def validate_config(config, items, logger):
|
||||||
|
"""
|
||||||
|
Validates if all items are available in the configuration.
|
||||||
|
|
||||||
|
config is the general dictionary with all the configurations.
|
||||||
|
items is a dict with per domain which attributes we require.
|
||||||
|
logger is the logger from the caller to log the errors to.
|
||||||
|
|
||||||
|
Returns True if all required items were found.
|
||||||
|
"""
|
||||||
|
errors_found = False
|
||||||
|
for domain in items.keys():
|
||||||
|
errors = [item for item in items[domain] if item not in config[domain]]
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
logger.error(
|
||||||
|
"Missing required configuration items in {}: {}".format(
|
||||||
|
domain, ", ".join(errors)))
|
||||||
|
|
||||||
|
errors_found = True
|
||||||
|
|
||||||
|
return not errors_found
|
||||||
|
|
||||||
|
|
||||||
# Reason why I decided to roll my own ThreadPool instead of using
|
# Reason why I decided to roll my own ThreadPool instead of using
|
||||||
# multiprocessing.dummy.pool or even better, use multiprocessing.pool and
|
# multiprocessing.dummy.pool or even better, use multiprocessing.pool and
|
||||||
# not be hurt by the GIL in the cpython interpreter:
|
# not be hurt by the GIL in the cpython interpreter:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue