mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Merge branch 'pep257-utils' into dev
This commit is contained in:
commit
7f1e181c9b
@ -0,0 +1 @@
|
|||||||
|
"""Init file for Home Assistant."""
|
@ -1,4 +1,4 @@
|
|||||||
""" Starts home assistant. """
|
"""Starts home assistant."""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@ -20,7 +20,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
|
|
||||||
def validate_python():
|
def validate_python():
|
||||||
""" Validate we're running the right Python version. """
|
"""Validate we're running the right Python version."""
|
||||||
major, minor = sys.version_info[:2]
|
major, minor = sys.version_info[:2]
|
||||||
req_major, req_minor = REQUIRED_PYTHON_VER
|
req_major, req_minor = REQUIRED_PYTHON_VER
|
||||||
|
|
||||||
@ -31,8 +31,7 @@ def validate_python():
|
|||||||
|
|
||||||
|
|
||||||
def ensure_config_path(config_dir):
|
def ensure_config_path(config_dir):
|
||||||
""" Validates configuration directory. """
|
"""Validate the configuration directory."""
|
||||||
|
|
||||||
lib_dir = os.path.join(config_dir, 'lib')
|
lib_dir = os.path.join(config_dir, 'lib')
|
||||||
|
|
||||||
# Test if configuration directory exists
|
# Test if configuration directory exists
|
||||||
@ -60,7 +59,7 @@ def ensure_config_path(config_dir):
|
|||||||
|
|
||||||
|
|
||||||
def ensure_config_file(config_dir):
|
def ensure_config_file(config_dir):
|
||||||
""" Ensure configuration file exists. """
|
"""Ensure configuration file exists."""
|
||||||
config_path = config_util.ensure_config_exists(config_dir)
|
config_path = config_util.ensure_config_exists(config_dir)
|
||||||
|
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
@ -71,7 +70,7 @@ def ensure_config_file(config_dir):
|
|||||||
|
|
||||||
|
|
||||||
def get_arguments():
|
def get_arguments():
|
||||||
""" Get parsed passed in arguments. """
|
"""Get parsed passed in arguments."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Home Assistant: Observe, Control, Automate.")
|
description="Home Assistant: Observe, Control, Automate.")
|
||||||
parser.add_argument('--version', action='version', version=__version__)
|
parser.add_argument('--version', action='version', version=__version__)
|
||||||
@ -136,25 +135,25 @@ def get_arguments():
|
|||||||
|
|
||||||
|
|
||||||
def daemonize():
|
def daemonize():
|
||||||
""" Move current process to daemon process """
|
"""Move current process to daemon process."""
|
||||||
# create first fork
|
# Create first fork
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid > 0:
|
if pid > 0:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# decouple fork
|
# Decouple fork
|
||||||
os.setsid()
|
os.setsid()
|
||||||
os.umask(0)
|
os.umask(0)
|
||||||
|
|
||||||
# create second fork
|
# Create second fork
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid > 0:
|
if pid > 0:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
def check_pid(pid_file):
|
def check_pid(pid_file):
|
||||||
""" Check that HA is not already running """
|
"""Check that HA is not already running."""
|
||||||
# check pid file
|
# Check pid file
|
||||||
try:
|
try:
|
||||||
pid = int(open(pid_file, 'r').readline())
|
pid = int(open(pid_file, 'r').readline())
|
||||||
except IOError:
|
except IOError:
|
||||||
@ -171,7 +170,7 @@ def check_pid(pid_file):
|
|||||||
|
|
||||||
|
|
||||||
def write_pid(pid_file):
|
def write_pid(pid_file):
|
||||||
""" Create PID File """
|
"""Create a PID File."""
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
try:
|
try:
|
||||||
open(pid_file, 'w').write(str(pid))
|
open(pid_file, 'w').write(str(pid))
|
||||||
@ -181,7 +180,7 @@ def write_pid(pid_file):
|
|||||||
|
|
||||||
|
|
||||||
def install_osx():
|
def install_osx():
|
||||||
""" Setup to run via launchd on OS X """
|
"""Setup to run via launchd on OS X."""
|
||||||
with os.popen('which hass') as inp:
|
with os.popen('which hass') as inp:
|
||||||
hass_path = inp.read().strip()
|
hass_path = inp.read().strip()
|
||||||
|
|
||||||
@ -213,7 +212,7 @@ def install_osx():
|
|||||||
|
|
||||||
|
|
||||||
def uninstall_osx():
|
def uninstall_osx():
|
||||||
""" Unload from launchd on OS X """
|
"""Unload from launchd on OS X."""
|
||||||
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
|
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
|
||||||
os.popen('launchctl unload ' + path)
|
os.popen('launchctl unload ' + path)
|
||||||
|
|
||||||
@ -221,9 +220,10 @@ def uninstall_osx():
|
|||||||
|
|
||||||
|
|
||||||
def setup_and_run_hass(config_dir, args, top_process=False):
|
def setup_and_run_hass(config_dir, args, top_process=False):
|
||||||
"""
|
"""Setup HASS and run.
|
||||||
Setup HASS and run. Block until stopped. Will assume it is running in a
|
|
||||||
subprocess unless top_process is set to true.
|
Block until stopped. Will assume it is running in a subprocess unless
|
||||||
|
top_process is set to true.
|
||||||
"""
|
"""
|
||||||
if args.demo_mode:
|
if args.demo_mode:
|
||||||
config = {
|
config = {
|
||||||
@ -243,7 +243,7 @@ def setup_and_run_hass(config_dir, args, top_process=False):
|
|||||||
|
|
||||||
if args.open_ui:
|
if args.open_ui:
|
||||||
def open_browser(event):
|
def open_browser(event):
|
||||||
""" Open the webinterface in a browser. """
|
"""Open the webinterface in a browser."""
|
||||||
if hass.config.api is not None:
|
if hass.config.api is not None:
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open(hass.config.api.base_url)
|
webbrowser.open(hass.config.api.base_url)
|
||||||
@ -259,12 +259,12 @@ def setup_and_run_hass(config_dir, args, top_process=False):
|
|||||||
|
|
||||||
|
|
||||||
def run_hass_process(hass_proc):
|
def run_hass_process(hass_proc):
|
||||||
""" Runs a child hass process. Returns True if it should be restarted. """
|
"""Run a child hass process. Returns True if it should be restarted."""
|
||||||
requested_stop = threading.Event()
|
requested_stop = threading.Event()
|
||||||
hass_proc.daemon = True
|
hass_proc.daemon = True
|
||||||
|
|
||||||
def request_stop(*args):
|
def request_stop(*args):
|
||||||
""" request hass stop, *args is for signal handler callback """
|
"""Request hass stop, *args is for signal handler callback."""
|
||||||
requested_stop.set()
|
requested_stop.set()
|
||||||
hass_proc.terminate()
|
hass_proc.terminate()
|
||||||
|
|
||||||
@ -289,7 +289,7 @@ def run_hass_process(hass_proc):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" Starts Home Assistant. """
|
"""Start Home Assistant."""
|
||||||
validate_python()
|
validate_python()
|
||||||
|
|
||||||
args = get_arguments()
|
args = get_arguments()
|
||||||
@ -297,7 +297,7 @@ def main():
|
|||||||
config_dir = os.path.join(os.getcwd(), args.config)
|
config_dir = os.path.join(os.getcwd(), args.config)
|
||||||
ensure_config_path(config_dir)
|
ensure_config_path(config_dir)
|
||||||
|
|
||||||
# os x launchd functions
|
# OS X launchd functions
|
||||||
if args.install_osx:
|
if args.install_osx:
|
||||||
install_osx()
|
install_osx()
|
||||||
return 0
|
return 0
|
||||||
@ -311,7 +311,7 @@ def main():
|
|||||||
install_osx()
|
install_osx()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# daemon functions
|
# Daemon functions
|
||||||
if args.pid_file:
|
if args.pid_file:
|
||||||
check_pid(args.pid_file)
|
check_pid(args.pid_file)
|
||||||
if args.daemon:
|
if args.daemon:
|
||||||
|
@ -34,8 +34,7 @@ ERROR_LOG_FILENAME = 'home-assistant.log'
|
|||||||
|
|
||||||
|
|
||||||
def setup_component(hass, domain, config=None):
|
def setup_component(hass, domain, config=None):
|
||||||
""" Setup a component and all its dependencies. """
|
"""Setup a component and all its dependencies."""
|
||||||
|
|
||||||
if domain in hass.config.components:
|
if domain in hass.config.components:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ def setup_component(hass, domain, config=None):
|
|||||||
|
|
||||||
|
|
||||||
def _handle_requirements(hass, component, name):
|
def _handle_requirements(hass, component, name):
|
||||||
""" Installs requirements for component. """
|
"""Install the requirements for a component."""
|
||||||
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
|
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -126,7 +125,7 @@ def _setup_component(hass, domain, config):
|
|||||||
|
|
||||||
|
|
||||||
def prepare_setup_platform(hass, config, domain, platform_name):
|
def prepare_setup_platform(hass, config, domain, platform_name):
|
||||||
""" Loads a platform and makes sure dependencies are setup. """
|
"""Load a platform and makes sure dependencies are setup."""
|
||||||
_ensure_loader_prepared(hass)
|
_ensure_loader_prepared(hass)
|
||||||
|
|
||||||
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
|
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
|
||||||
@ -158,7 +157,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
|
|||||||
|
|
||||||
|
|
||||||
def mount_local_lib_path(config_dir):
|
def mount_local_lib_path(config_dir):
|
||||||
""" Add local library to Python Path """
|
"""Add local library to Python Path."""
|
||||||
sys.path.insert(0, os.path.join(config_dir, 'lib'))
|
sys.path.insert(0, os.path.join(config_dir, 'lib'))
|
||||||
|
|
||||||
|
|
||||||
@ -166,8 +165,7 @@ def mount_local_lib_path(config_dir):
|
|||||||
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
||||||
verbose=False, daemon=False, skip_pip=False,
|
verbose=False, daemon=False, skip_pip=False,
|
||||||
log_rotate_days=None):
|
log_rotate_days=None):
|
||||||
"""
|
"""Try to configure Home Assistant from a config dict.
|
||||||
Tries to configure Home Assistant from a config dict.
|
|
||||||
|
|
||||||
Dynamically loads required components and its dependencies.
|
Dynamically loads required components and its dependencies.
|
||||||
"""
|
"""
|
||||||
@ -209,7 +207,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
|||||||
|
|
||||||
_LOGGER.info('Home Assistant core initialized')
|
_LOGGER.info('Home Assistant core initialized')
|
||||||
|
|
||||||
# give event decorators access to HASS
|
# Give event decorators access to HASS
|
||||||
event_decorators.HASS = hass
|
event_decorators.HASS = hass
|
||||||
service.HASS = hass
|
service.HASS = hass
|
||||||
|
|
||||||
@ -222,9 +220,9 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
|||||||
|
|
||||||
def from_config_file(config_path, hass=None, verbose=False, daemon=False,
|
def from_config_file(config_path, hass=None, verbose=False, daemon=False,
|
||||||
skip_pip=True, log_rotate_days=None):
|
skip_pip=True, log_rotate_days=None):
|
||||||
"""
|
"""Read the configuration file and try to start all the functionality.
|
||||||
Reads the configuration file and tries to start all the required
|
|
||||||
functionality. Will add functionality to 'hass' parameter if given,
|
Will add functionality to 'hass' parameter if given,
|
||||||
instantiates a new Home Assistant object if 'hass' is not given.
|
instantiates a new Home Assistant object if 'hass' is not given.
|
||||||
"""
|
"""
|
||||||
if hass is None:
|
if hass is None:
|
||||||
@ -244,7 +242,7 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
|
|||||||
|
|
||||||
|
|
||||||
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
|
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
|
||||||
""" Setup the logging for home assistant. """
|
"""Setup the logging."""
|
||||||
if not daemon:
|
if not daemon:
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
|
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
|
||||||
@ -297,7 +295,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
|
|||||||
|
|
||||||
|
|
||||||
def process_ha_config_upgrade(hass):
|
def process_ha_config_upgrade(hass):
|
||||||
""" Upgrade config if necessary. """
|
"""Upgrade config if necessary."""
|
||||||
version_path = hass.config.path('.HA_VERSION')
|
version_path = hass.config.path('.HA_VERSION')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -322,11 +320,11 @@ def process_ha_config_upgrade(hass):
|
|||||||
|
|
||||||
|
|
||||||
def process_ha_core_config(hass, config):
|
def process_ha_core_config(hass, config):
|
||||||
""" Processes the [homeassistant] section from the config. """
|
"""Process the [homeassistant] section from the config."""
|
||||||
hac = hass.config
|
hac = hass.config
|
||||||
|
|
||||||
def set_time_zone(time_zone_str):
|
def set_time_zone(time_zone_str):
|
||||||
""" Helper method to set time zone in HA. """
|
"""Helper method to set time zone."""
|
||||||
if time_zone_str is None:
|
if time_zone_str is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -397,6 +395,6 @@ def process_ha_core_config(hass, config):
|
|||||||
|
|
||||||
|
|
||||||
def _ensure_loader_prepared(hass):
|
def _ensure_loader_prepared(hass):
|
||||||
""" Ensure Home Assistant loader is prepared. """
|
"""Ensure Home Assistant loader is prepared."""
|
||||||
if not loader.PREPARED:
|
if not loader.PREPARED:
|
||||||
loader.prepare(hass)
|
loader.prepare(hass)
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
"""
|
"""Module to help with parsing and generating configuration files."""
|
||||||
homeassistant.config
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Module to help with parsing and generating configuration files.
|
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -43,16 +38,18 @@ DEFAULT_COMPONENTS = {
|
|||||||
|
|
||||||
|
|
||||||
def get_default_config_dir():
|
def get_default_config_dir():
|
||||||
""" Put together the default configuration directory based on OS. """
|
"""Put together the default configuration directory based on OS."""
|
||||||
data_dir = os.getenv('APPDATA') if os.name == "nt" \
|
data_dir = os.getenv('APPDATA') if os.name == "nt" \
|
||||||
else os.path.expanduser('~')
|
else os.path.expanduser('~')
|
||||||
return os.path.join(data_dir, CONFIG_DIR_NAME)
|
return os.path.join(data_dir, CONFIG_DIR_NAME)
|
||||||
|
|
||||||
|
|
||||||
def ensure_config_exists(config_dir, detect_location=True):
|
def ensure_config_exists(config_dir, detect_location=True):
|
||||||
""" Ensures a config file exists in given config dir.
|
"""Ensure a config file exists in given configuration directory.
|
||||||
Creating a default one if needed.
|
|
||||||
Returns path to the config file. """
|
Creating a default one if needed.
|
||||||
|
Return path to the config file.
|
||||||
|
"""
|
||||||
config_path = find_config_file(config_dir)
|
config_path = find_config_file(config_dir)
|
||||||
|
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
@ -64,8 +61,10 @@ def ensure_config_exists(config_dir, detect_location=True):
|
|||||||
|
|
||||||
|
|
||||||
def create_default_config(config_dir, detect_location=True):
|
def create_default_config(config_dir, detect_location=True):
|
||||||
""" Creates a default configuration file in given config dir.
|
"""Create a default configuration file in given configuration directory.
|
||||||
Returns path to new config file if success, None if failed. """
|
|
||||||
|
Return path to new config file if success, None if failed.
|
||||||
|
"""
|
||||||
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
||||||
|
|
||||||
info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG}
|
info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG}
|
||||||
@ -108,14 +107,14 @@ def create_default_config(config_dir, detect_location=True):
|
|||||||
|
|
||||||
|
|
||||||
def find_config_file(config_dir):
|
def find_config_file(config_dir):
|
||||||
""" Looks in given directory for supported config files. """
|
"""Look in given directory for supported configuration files."""
|
||||||
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
||||||
|
|
||||||
return config_path if os.path.isfile(config_path) else None
|
return config_path if os.path.isfile(config_path) else None
|
||||||
|
|
||||||
|
|
||||||
def load_yaml_config_file(config_path):
|
def load_yaml_config_file(config_path):
|
||||||
""" Parse a YAML configuration file. """
|
"""Parse a YAML configuration file."""
|
||||||
conf_dict = load_yaml(config_path)
|
conf_dict = load_yaml(config_path)
|
||||||
|
|
||||||
if not isinstance(conf_dict, dict):
|
if not isinstance(conf_dict, dict):
|
||||||
|
@ -132,12 +132,13 @@ class JobPriority(util.OrderedEnum):
|
|||||||
|
|
||||||
|
|
||||||
class EventOrigin(enum.Enum):
|
class EventOrigin(enum.Enum):
|
||||||
"""Represents origin of an event."""
|
"""Represent the origin of an event."""
|
||||||
|
|
||||||
local = "LOCAL"
|
local = "LOCAL"
|
||||||
remote = "REMOTE"
|
remote = "REMOTE"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
"""Return the event."""
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
@ -166,6 +167,7 @@ class Event(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return the representation."""
|
||||||
# pylint: disable=maybe-no-member
|
# pylint: disable=maybe-no-member
|
||||||
if self.data:
|
if self.data:
|
||||||
return "<Event {}[{}]: {}>".format(
|
return "<Event {}[{}]: {}>".format(
|
||||||
@ -176,6 +178,7 @@ class Event(object):
|
|||||||
str(self.origin)[0])
|
str(self.origin)[0])
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
"""Return the comparison."""
|
||||||
return (self.__class__ == other.__class__ and
|
return (self.__class__ == other.__class__ and
|
||||||
self.event_type == other.event_type and
|
self.event_type == other.event_type and
|
||||||
self.data == other.data and
|
self.data == other.data and
|
||||||
@ -246,7 +249,7 @@ class EventBus(object):
|
|||||||
"""
|
"""
|
||||||
@ft.wraps(listener)
|
@ft.wraps(listener)
|
||||||
def onetime_listener(event):
|
def onetime_listener(event):
|
||||||
"""Remove listener from eventbus and then fires listener."""
|
"""Remove listener from eventbus and then fire listener."""
|
||||||
if hasattr(onetime_listener, 'run'):
|
if hasattr(onetime_listener, 'run'):
|
||||||
return
|
return
|
||||||
# Set variable so that we will never run twice.
|
# Set variable so that we will never run twice.
|
||||||
@ -281,8 +284,7 @@ class EventBus(object):
|
|||||||
|
|
||||||
|
|
||||||
class State(object):
|
class State(object):
|
||||||
"""
|
"""Object to represent a state within the state machine.
|
||||||
Object to represent a state within the state machine.
|
|
||||||
|
|
||||||
entity_id: the entity that is represented.
|
entity_id: the entity that is represented.
|
||||||
state: the state of the entity
|
state: the state of the entity
|
||||||
@ -369,12 +371,14 @@ class State(object):
|
|||||||
json_dict.get('attributes'), last_changed, last_updated)
|
json_dict.get('attributes'), last_changed, last_updated)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
"""Return the comparison of the state."""
|
||||||
return (self.__class__ == other.__class__ and
|
return (self.__class__ == other.__class__ and
|
||||||
self.entity_id == other.entity_id and
|
self.entity_id == other.entity_id and
|
||||||
self.state == other.state and
|
self.state == other.state and
|
||||||
self.attributes == other.attributes)
|
self.attributes == other.attributes)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return the representation of the states."""
|
||||||
attr = "; {}".format(util.repr_helper(self.attributes)) \
|
attr = "; {}".format(util.repr_helper(self.attributes)) \
|
||||||
if self.attributes else ""
|
if self.attributes else ""
|
||||||
|
|
||||||
@ -524,6 +528,7 @@ class ServiceCall(object):
|
|||||||
self.call_id = call_id
|
self.call_id = call_id
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return the represenation of the service."""
|
||||||
if self.data:
|
if self.data:
|
||||||
return "<ServiceCall {}.{}: {}>".format(
|
return "<ServiceCall {}.{}: {}>".format(
|
||||||
self.domain, self.service, util.repr_helper(self.data))
|
self.domain, self.service, util.repr_helper(self.data))
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
""" Exceptions used by Home Assistant """
|
"""Exceptions used by Home Assistant."""
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantError(Exception):
|
class HomeAssistantError(Exception):
|
||||||
""" General Home Assistant exception occured. """
|
"""General Home Assistant exception occurred."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidEntityFormatError(HomeAssistantError):
|
class InvalidEntityFormatError(HomeAssistantError):
|
||||||
""" When an invalid formatted entity is encountered. """
|
"""When an invalid formatted entity is encountered."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoEntitySpecifiedError(HomeAssistantError):
|
class NoEntitySpecifiedError(HomeAssistantError):
|
||||||
""" When no entity is specified. """
|
"""When no entity is specified."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TemplateError(HomeAssistantError):
|
class TemplateError(HomeAssistantError):
|
||||||
""" Error during template rendering. """
|
"""Error during template rendering."""
|
||||||
|
|
||||||
def __init__(self, exception):
|
def __init__(self, exception):
|
||||||
|
"""Initalize the error."""
|
||||||
super().__init__('{}: {}'.format(exception.__class__.__name__,
|
super().__init__('{}: {}'.format(exception.__class__.__name__,
|
||||||
exception))
|
exception))
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
"""
|
"""Helper methods for components within Home Assistant."""
|
||||||
Helper methods for components within Home Assistant.
|
|
||||||
"""
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
|
||||||
|
|
||||||
def validate_config(config, items, logger):
|
def validate_config(config, items, logger):
|
||||||
"""
|
"""Validate if all items are available in the configuration.
|
||||||
Validates if all items are available in the configuration.
|
|
||||||
|
|
||||||
config is the general dictionary with all the configurations.
|
config is the general dictionary with all the configurations.
|
||||||
items is a dict with per domain which attributes we require.
|
items is a dict with per domain which attributes we require.
|
||||||
logger is the logger from the caller to log the errors to.
|
logger is the logger from the caller to log the errors to.
|
||||||
|
|
||||||
Returns True if all required items were found.
|
Return True if all required items were found.
|
||||||
"""
|
"""
|
||||||
errors_found = False
|
errors_found = False
|
||||||
for domain in items.keys():
|
for domain in items.keys():
|
||||||
@ -33,8 +30,8 @@ def validate_config(config, items, logger):
|
|||||||
|
|
||||||
|
|
||||||
def config_per_platform(config, domain, logger):
|
def config_per_platform(config, domain, logger):
|
||||||
"""
|
"""Generator to break a component config into different platforms.
|
||||||
Generator to break a component config into different platforms.
|
|
||||||
For example, will find 'switch', 'switch 2', 'switch 3', .. etc
|
For example, will find 'switch', 'switch 2', 'switch 3', .. etc
|
||||||
"""
|
"""
|
||||||
config_key = domain
|
config_key = domain
|
||||||
@ -59,6 +56,6 @@ def config_per_platform(config, domain, logger):
|
|||||||
|
|
||||||
|
|
||||||
def extract_domain_configs(config, domain):
|
def extract_domain_configs(config, domain):
|
||||||
""" Extract keys from config for given domain name. """
|
"""Extract keys from config for given domain name."""
|
||||||
pattern = re.compile(r'^{}(| .+)$'.format(domain))
|
pattern = re.compile(r'^{}(| .+)$'.format(domain))
|
||||||
return [key for key in config.keys() if pattern.match(key)]
|
return [key for key in config.keys() if pattern.match(key)]
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
"""
|
"""An abstract class for entities in HA."""
|
||||||
homeassistant.helpers.entity.
|
|
||||||
|
|
||||||
Provides ABC for entities in HA.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -49,15 +45,12 @@ class Entity(object):
|
|||||||
"""ABC for Home Assistant entities."""
|
"""ABC for Home Assistant entities."""
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
|
|
||||||
# SAFE TO OVERWRITE
|
# SAFE TO OVERWRITE
|
||||||
# The properties and methods here are safe to overwrite when inherting this
|
# The properties and methods here are safe to overwrite when inherting this
|
||||||
# class. These may be used to customize the behavior of the entity.
|
# class. These may be used to customize the behavior of the entity.
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""
|
"""Return True if entity has to be polled for state.
|
||||||
Return True if entity has to be polled for state.
|
|
||||||
|
|
||||||
False if entity pushes its state to HA.
|
False if entity pushes its state to HA.
|
||||||
"""
|
"""
|
||||||
@ -80,8 +73,7 @@ class Entity(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
"""
|
"""Return the state attributes.
|
||||||
Return the state attributes.
|
|
||||||
|
|
||||||
Implemented by component base class.
|
Implemented by component base class.
|
||||||
"""
|
"""
|
||||||
@ -89,8 +81,7 @@ class Entity(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""
|
"""Return device specific state attributes.
|
||||||
Return device specific state attributes.
|
|
||||||
|
|
||||||
Implemented by platform classes.
|
Implemented by platform classes.
|
||||||
"""
|
"""
|
||||||
@ -140,8 +131,7 @@ class Entity(object):
|
|||||||
hass = None
|
hass = None
|
||||||
|
|
||||||
def update_ha_state(self, force_refresh=False):
|
def update_ha_state(self, force_refresh=False):
|
||||||
"""
|
"""Update Home Assistant with current state of entity.
|
||||||
Update Home Assistant with current state of entity.
|
|
||||||
|
|
||||||
If force_refresh == True will update entity before setting state.
|
If force_refresh == True will update entity before setting state.
|
||||||
"""
|
"""
|
||||||
@ -176,10 +166,10 @@ class Entity(object):
|
|||||||
self._attr_setter('hidden', bool, ATTR_HIDDEN, attr)
|
self._attr_setter('hidden', bool, ATTR_HIDDEN, attr)
|
||||||
self._attr_setter('assumed_state', bool, ATTR_ASSUMED_STATE, attr)
|
self._attr_setter('assumed_state', bool, ATTR_ASSUMED_STATE, attr)
|
||||||
|
|
||||||
# overwrite properties that have been set in the config file
|
# Overwrite properties that have been set in the config file.
|
||||||
attr.update(_OVERWRITE.get(self.entity_id, {}))
|
attr.update(_OVERWRITE.get(self.entity_id, {}))
|
||||||
|
|
||||||
# remove hidden property if false so it won't show up
|
# Remove hidden property if false so it won't show up.
|
||||||
if not attr.get(ATTR_HIDDEN, True):
|
if not attr.get(ATTR_HIDDEN, True):
|
||||||
attr.pop(ATTR_HIDDEN)
|
attr.pop(ATTR_HIDDEN)
|
||||||
|
|
||||||
@ -210,16 +200,17 @@ class Entity(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
"""Return the comparison."""
|
||||||
return (isinstance(other, Entity) and
|
return (isinstance(other, Entity) and
|
||||||
other.unique_id == self.unique_id)
|
other.unique_id == self.unique_id)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return the representation."""
|
||||||
return "<Entity {}: {}>".format(self.name, self.state)
|
return "<Entity {}: {}>".format(self.name, self.state)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def overwrite_attribute(entity_id, attrs, vals):
|
def overwrite_attribute(entity_id, attrs, vals):
|
||||||
"""
|
"""Overwrite any attribute of an entity.
|
||||||
Overwrite any attribute of an entity.
|
|
||||||
|
|
||||||
This function should receive a list of attributes and a
|
This function should receive a list of attributes and a
|
||||||
list of values. Set attribute to None to remove any overwritten
|
list of values. Set attribute to None to remove any overwritten
|
||||||
@ -236,7 +227,6 @@ class ToggleEntity(Entity):
|
|||||||
"""ABC for entities that can be turned on and off."""
|
"""ABC for entities that can be turned on and off."""
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
@ -244,7 +234,7 @@ class ToggleEntity(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""True if entity is on."""
|
"""Return True if entity is on."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
|
@ -41,8 +41,7 @@ class EntityComponent(object):
|
|||||||
self.scan_interval).add_entities
|
self.scan_interval).add_entities
|
||||||
|
|
||||||
def setup(self, config):
|
def setup(self, config):
|
||||||
"""
|
"""Set up a full entity component.
|
||||||
Set up a full entity component.
|
|
||||||
|
|
||||||
Loads the platforms from the config and will listen for supported
|
Loads the platforms from the config and will listen for supported
|
||||||
discovered platforms.
|
discovered platforms.
|
||||||
@ -63,8 +62,7 @@ class EntityComponent(object):
|
|||||||
info))
|
info))
|
||||||
|
|
||||||
def extract_from_service(self, service):
|
def extract_from_service(self, service):
|
||||||
"""
|
"""Extract all known entities from a service call.
|
||||||
Extract all known entities from a service call.
|
|
||||||
|
|
||||||
Will return all entities if no entities specified in call.
|
Will return all entities if no entities specified in call.
|
||||||
Will return an empty list if entities specified but unknown.
|
Will return an empty list if entities specified but unknown.
|
||||||
@ -134,6 +132,7 @@ class EntityPlatform(object):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
def __init__(self, component, scan_interval):
|
def __init__(self, component, scan_interval):
|
||||||
|
"""Initalize the entity platform."""
|
||||||
self.component = component
|
self.component = component
|
||||||
self.scan_interval = scan_interval
|
self.scan_interval = scan_interval
|
||||||
self.platform_entities = []
|
self.platform_entities = []
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Helpers for listening to events."""
|
||||||
Helpers for listening to events
|
|
||||||
"""
|
|
||||||
import functools as ft
|
import functools as ft
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
@ -11,8 +9,8 @@ from ..util import dt as dt_util
|
|||||||
|
|
||||||
def track_state_change(hass, entity_ids, action, from_state=None,
|
def track_state_change(hass, entity_ids, action, from_state=None,
|
||||||
to_state=None):
|
to_state=None):
|
||||||
"""
|
"""Track specific state changes.
|
||||||
Track specific state changes.
|
|
||||||
entity_ids, from_state and to_state can be string or list.
|
entity_ids, from_state and to_state can be string or list.
|
||||||
Use list to match multiple.
|
Use list to match multiple.
|
||||||
|
|
||||||
@ -30,7 +28,7 @@ def track_state_change(hass, entity_ids, action, from_state=None,
|
|||||||
|
|
||||||
@ft.wraps(action)
|
@ft.wraps(action)
|
||||||
def state_change_listener(event):
|
def state_change_listener(event):
|
||||||
""" The listener that listens for specific state changes. """
|
"""The listener that listens for specific state changes."""
|
||||||
if event.data['entity_id'] not in entity_ids:
|
if event.data['entity_id'] not in entity_ids:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -55,29 +53,25 @@ def track_state_change(hass, entity_ids, action, from_state=None,
|
|||||||
|
|
||||||
|
|
||||||
def track_point_in_time(hass, action, point_in_time):
|
def track_point_in_time(hass, action, point_in_time):
|
||||||
"""
|
"""Add a listener that fires once after a spefic point in time."""
|
||||||
Adds a listener that fires once after a spefic point in time.
|
|
||||||
"""
|
|
||||||
utc_point_in_time = dt_util.as_utc(point_in_time)
|
utc_point_in_time = dt_util.as_utc(point_in_time)
|
||||||
|
|
||||||
@ft.wraps(action)
|
@ft.wraps(action)
|
||||||
def utc_converter(utc_now):
|
def utc_converter(utc_now):
|
||||||
""" Converts passed in UTC now to local now. """
|
"""Convert passed in UTC now to local now."""
|
||||||
action(dt_util.as_local(utc_now))
|
action(dt_util.as_local(utc_now))
|
||||||
|
|
||||||
return track_point_in_utc_time(hass, utc_converter, utc_point_in_time)
|
return track_point_in_utc_time(hass, utc_converter, utc_point_in_time)
|
||||||
|
|
||||||
|
|
||||||
def track_point_in_utc_time(hass, action, point_in_time):
|
def track_point_in_utc_time(hass, action, point_in_time):
|
||||||
"""
|
"""Add a listener that fires once after a specific point in UTC time."""
|
||||||
Adds a listener that fires once after a specific point in UTC time.
|
|
||||||
"""
|
|
||||||
# Ensure point_in_time is UTC
|
# Ensure point_in_time is UTC
|
||||||
point_in_time = dt_util.as_utc(point_in_time)
|
point_in_time = dt_util.as_utc(point_in_time)
|
||||||
|
|
||||||
@ft.wraps(action)
|
@ft.wraps(action)
|
||||||
def point_in_time_listener(event):
|
def point_in_time_listener(event):
|
||||||
""" Listens for matching time_changed events. """
|
"""Listen for matching time_changed events."""
|
||||||
now = event.data[ATTR_NOW]
|
now = event.data[ATTR_NOW]
|
||||||
|
|
||||||
if now >= point_in_time and \
|
if now >= point_in_time and \
|
||||||
@ -100,14 +94,12 @@ def track_point_in_utc_time(hass, action, point_in_time):
|
|||||||
|
|
||||||
|
|
||||||
def track_sunrise(hass, action, offset=None):
|
def track_sunrise(hass, action, offset=None):
|
||||||
"""
|
"""Add a listener that will fire a specified offset from sunrise daily."""
|
||||||
Adds a listener that will fire a specified offset from sunrise daily.
|
|
||||||
"""
|
|
||||||
from homeassistant.components import sun
|
from homeassistant.components import sun
|
||||||
offset = offset or timedelta()
|
offset = offset or timedelta()
|
||||||
|
|
||||||
def next_rise():
|
def next_rise():
|
||||||
""" Returns next sunrise. """
|
"""Return the next sunrise."""
|
||||||
next_time = sun.next_rising_utc(hass) + offset
|
next_time = sun.next_rising_utc(hass) + offset
|
||||||
|
|
||||||
while next_time < dt_util.utcnow():
|
while next_time < dt_util.utcnow():
|
||||||
@ -116,7 +108,7 @@ def track_sunrise(hass, action, offset=None):
|
|||||||
return next_time
|
return next_time
|
||||||
|
|
||||||
def sunrise_automation_listener(now):
|
def sunrise_automation_listener(now):
|
||||||
""" Called when it's time for action. """
|
"""Called when it's time for action."""
|
||||||
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
|
||||||
action()
|
action()
|
||||||
|
|
||||||
@ -124,14 +116,12 @@ def track_sunrise(hass, action, offset=None):
|
|||||||
|
|
||||||
|
|
||||||
def track_sunset(hass, action, offset=None):
|
def track_sunset(hass, action, offset=None):
|
||||||
"""
|
"""Add a listener that will fire a specified offset from sunset daily."""
|
||||||
Adds a listener that will fire a specified offset from sunset daily.
|
|
||||||
"""
|
|
||||||
from homeassistant.components import sun
|
from homeassistant.components import sun
|
||||||
offset = offset or timedelta()
|
offset = offset or timedelta()
|
||||||
|
|
||||||
def next_set():
|
def next_set():
|
||||||
""" Returns next sunrise. """
|
"""Return next sunrise."""
|
||||||
next_time = sun.next_setting_utc(hass) + offset
|
next_time = sun.next_setting_utc(hass) + offset
|
||||||
|
|
||||||
while next_time < dt_util.utcnow():
|
while next_time < dt_util.utcnow():
|
||||||
@ -140,7 +130,7 @@ def track_sunset(hass, action, offset=None):
|
|||||||
return next_time
|
return next_time
|
||||||
|
|
||||||
def sunset_automation_listener(now):
|
def sunset_automation_listener(now):
|
||||||
""" Called when it's time for action. """
|
"""Called when it's time for action."""
|
||||||
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
track_point_in_utc_time(hass, sunset_automation_listener, next_set())
|
||||||
action()
|
action()
|
||||||
|
|
||||||
@ -150,13 +140,13 @@ def track_sunset(hass, action, offset=None):
|
|||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
||||||
hour=None, minute=None, second=None, local=False):
|
hour=None, minute=None, second=None, local=False):
|
||||||
""" Adds a listener that will fire if time matches a pattern. """
|
"""Add a listener that will fire if time matches a pattern."""
|
||||||
# We do not have to wrap the function with time pattern matching logic
|
# We do not have to wrap the function with time pattern matching logic
|
||||||
# if no pattern given
|
# if no pattern given
|
||||||
if all(val is None for val in (year, month, day, hour, minute, second)):
|
if all(val is None for val in (year, month, day, hour, minute, second)):
|
||||||
@ft.wraps(action)
|
@ft.wraps(action)
|
||||||
def time_change_listener(event):
|
def time_change_listener(event):
|
||||||
""" Fires every time event that comes in. """
|
"""Fire every time event that comes in."""
|
||||||
action(event.data[ATTR_NOW])
|
action(event.data[ATTR_NOW])
|
||||||
|
|
||||||
hass.bus.listen(EVENT_TIME_CHANGED, time_change_listener)
|
hass.bus.listen(EVENT_TIME_CHANGED, time_change_listener)
|
||||||
@ -168,7 +158,7 @@ def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
|||||||
|
|
||||||
@ft.wraps(action)
|
@ft.wraps(action)
|
||||||
def pattern_time_change_listener(event):
|
def pattern_time_change_listener(event):
|
||||||
""" Listens for matching time_changed events. """
|
"""Listen for matching time_changed events."""
|
||||||
now = event.data[ATTR_NOW]
|
now = event.data[ATTR_NOW]
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
@ -192,13 +182,13 @@ def track_utc_time_change(hass, action, year=None, month=None, day=None,
|
|||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def track_time_change(hass, action, year=None, month=None, day=None,
|
def track_time_change(hass, action, year=None, month=None, day=None,
|
||||||
hour=None, minute=None, second=None):
|
hour=None, minute=None, second=None):
|
||||||
""" Adds a listener that will fire if UTC time matches a pattern. """
|
"""Add a listener that will fire if UTC time matches a pattern."""
|
||||||
track_utc_time_change(hass, action, year, month, day, hour, minute, second,
|
track_utc_time_change(hass, action, year, month, day, hour, minute, second,
|
||||||
local=True)
|
local=True)
|
||||||
|
|
||||||
|
|
||||||
def _process_match_param(parameter):
|
def _process_match_param(parameter):
|
||||||
""" Wraps parameter in a tuple if it is not one and returns it. """
|
"""Wrap parameter in a tuple if it is not one and returns it."""
|
||||||
if parameter is None or parameter == MATCH_ALL:
|
if parameter is None or parameter == MATCH_ALL:
|
||||||
return MATCH_ALL
|
return MATCH_ALL
|
||||||
elif isinstance(parameter, str) and parameter.startswith('/'):
|
elif isinstance(parameter, str) and parameter.startswith('/'):
|
||||||
@ -210,7 +200,7 @@ def _process_match_param(parameter):
|
|||||||
|
|
||||||
|
|
||||||
def _matcher(subject, pattern):
|
def _matcher(subject, pattern):
|
||||||
""" Returns True if subject matches the pattern.
|
"""Return True if subject matches the pattern.
|
||||||
|
|
||||||
Pattern is either a tuple of allowed subjects or a `MATCH_ALL`.
|
Pattern is either a tuple of allowed subjects or a `MATCH_ALL`.
|
||||||
"""
|
"""
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
""" Event Decorators for custom components """
|
"""Event Decorators for custom components."""
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from homeassistant.helpers import event
|
from homeassistant.helpers import event
|
||||||
@ -8,10 +7,9 @@ HASS = None
|
|||||||
|
|
||||||
|
|
||||||
def track_state_change(entity_ids, from_state=None, to_state=None):
|
def track_state_change(entity_ids, from_state=None, to_state=None):
|
||||||
""" Decorator factory to track state changes for entity id """
|
"""Decorator factory to track state changes for entity id."""
|
||||||
|
|
||||||
def track_state_change_decorator(action):
|
def track_state_change_decorator(action):
|
||||||
""" Decorator to track state changes """
|
"""Decorator to track state changes."""
|
||||||
event.track_state_change(HASS, entity_ids,
|
event.track_state_change(HASS, entity_ids,
|
||||||
functools.partial(action, HASS),
|
functools.partial(action, HASS),
|
||||||
from_state, to_state)
|
from_state, to_state)
|
||||||
@ -21,10 +19,9 @@ def track_state_change(entity_ids, from_state=None, to_state=None):
|
|||||||
|
|
||||||
|
|
||||||
def track_sunrise(offset=None):
|
def track_sunrise(offset=None):
|
||||||
""" Decorator factory to track sunrise events """
|
"""Decorator factory to track sunrise events."""
|
||||||
|
|
||||||
def track_sunrise_decorator(action):
|
def track_sunrise_decorator(action):
|
||||||
""" Decorator to track sunrise events """
|
"""Decorator to track sunrise events."""
|
||||||
event.track_sunrise(HASS,
|
event.track_sunrise(HASS,
|
||||||
functools.partial(action, HASS),
|
functools.partial(action, HASS),
|
||||||
offset)
|
offset)
|
||||||
@ -34,10 +31,9 @@ def track_sunrise(offset=None):
|
|||||||
|
|
||||||
|
|
||||||
def track_sunset(offset=None):
|
def track_sunset(offset=None):
|
||||||
""" Decorator factory to track sunset events """
|
"""Decorator factory to track sunset events."""
|
||||||
|
|
||||||
def track_sunset_decorator(action):
|
def track_sunset_decorator(action):
|
||||||
""" Decorator to track sunset events """
|
"""Decorator to track sunset events."""
|
||||||
event.track_sunset(HASS,
|
event.track_sunset(HASS,
|
||||||
functools.partial(action, HASS),
|
functools.partial(action, HASS),
|
||||||
offset)
|
offset)
|
||||||
@ -49,10 +45,9 @@ def track_sunset(offset=None):
|
|||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def track_time_change(year=None, month=None, day=None, hour=None, minute=None,
|
def track_time_change(year=None, month=None, day=None, hour=None, minute=None,
|
||||||
second=None):
|
second=None):
|
||||||
""" Decorator factory to track time changes """
|
"""Decorator factory to track time changes."""
|
||||||
|
|
||||||
def track_time_change_decorator(action):
|
def track_time_change_decorator(action):
|
||||||
""" Decorator to track time changes """
|
"""Decorator to track time changes."""
|
||||||
event.track_time_change(HASS,
|
event.track_time_change(HASS,
|
||||||
functools.partial(action, HASS),
|
functools.partial(action, HASS),
|
||||||
year, month, day, hour, minute, second)
|
year, month, day, hour, minute, second)
|
||||||
@ -64,10 +59,9 @@ def track_time_change(year=None, month=None, day=None, hour=None, minute=None,
|
|||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def track_utc_time_change(year=None, month=None, day=None, hour=None,
|
def track_utc_time_change(year=None, month=None, day=None, hour=None,
|
||||||
minute=None, second=None):
|
minute=None, second=None):
|
||||||
""" Decorator factory to track time changes """
|
"""Decorator factory to track time changes."""
|
||||||
|
|
||||||
def track_utc_time_change_decorator(action):
|
def track_utc_time_change_decorator(action):
|
||||||
""" Decorator to track time changes """
|
"""Decorator to track time changes."""
|
||||||
event.track_utc_time_change(HASS,
|
event.track_utc_time_change(HASS,
|
||||||
functools.partial(action, HASS),
|
functools.partial(action, HASS),
|
||||||
year, month, day, hour, minute, second)
|
year, month, day, hour, minute, second)
|
||||||
|
@ -16,10 +16,9 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def service(domain, service_name):
|
def service(domain, service_name):
|
||||||
""" Decorator factory to register a service """
|
"""Decorator factory to register a service."""
|
||||||
|
|
||||||
def register_service_decorator(action):
|
def register_service_decorator(action):
|
||||||
""" Decorator to register a service """
|
"""Decorator to register a service."""
|
||||||
HASS.services.register(domain, service_name,
|
HASS.services.register(domain, service_name,
|
||||||
functools.partial(action, HASS))
|
functools.partial(action, HASS))
|
||||||
return action
|
return action
|
||||||
@ -60,8 +59,8 @@ def call_from_config(hass, config, blocking=False):
|
|||||||
|
|
||||||
|
|
||||||
def extract_entity_ids(hass, service_call):
|
def extract_entity_ids(hass, service_call):
|
||||||
"""
|
"""Helper method to extract a list of entity ids from a service call.
|
||||||
Helper method to extract a list of entity ids from a service call.
|
|
||||||
Will convert group entity ids to the entity ids it represents.
|
Will convert group entity ids to the entity ids it represents.
|
||||||
"""
|
"""
|
||||||
if not (service_call.data and ATTR_ENTITY_ID in service_call.data):
|
if not (service_call.data and ATTR_ENTITY_ID in service_call.data):
|
||||||
|
@ -18,10 +18,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods, attribute-defined-outside-init
|
# pylint: disable=too-few-public-methods, attribute-defined-outside-init
|
||||||
class TrackStates(object):
|
class TrackStates(object):
|
||||||
"""
|
"""Record the time when the with-block is entered.
|
||||||
Records the time when the with-block is entered. Will add all states
|
|
||||||
that have changed since the start time to the return list when with-block
|
Will add all states that have changed since the start time to the return
|
||||||
is exited.
|
list when with-block is exited.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hass):
|
def __init__(self, hass):
|
||||||
@ -100,7 +100,6 @@ def state_as_number(state):
|
|||||||
|
|
||||||
Raises ValueError if this is not possible.
|
Raises ValueError if this is not possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON,
|
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON,
|
||||||
STATE_OPEN):
|
STATE_OPEN):
|
||||||
return 1
|
return 1
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
"""
|
"""Methods to help handle temperature in Home Assistant."""
|
||||||
homeassistant.helpers.temperature
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Methods to help handle temperature in Home Assistant.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import homeassistant.util.temperature as temp_util
|
import homeassistant.util.temperature as temp_util
|
||||||
from homeassistant.const import TEMP_CELCIUS
|
from homeassistant.const import TEMP_CELCIUS
|
||||||
|
|
||||||
|
|
||||||
def convert(temperature, unit, to_unit):
|
def convert(temperature, unit, to_unit):
|
||||||
""" Converts temperature to correct unit. """
|
"""Convert temperature to correct unit."""
|
||||||
if unit == to_unit or unit is None or to_unit is None:
|
if unit == to_unit or unit is None or to_unit is None:
|
||||||
return temperature
|
return temperature
|
||||||
elif unit == TEMP_CELCIUS:
|
elif unit == TEMP_CELCIUS:
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Template helper methods for rendering strings with HA data."""
|
||||||
Template helper methods for rendering strings with HA data.
|
|
||||||
"""
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@ -21,8 +19,8 @@ _SENTINEL = object()
|
|||||||
|
|
||||||
def render_with_possible_json_value(hass, template, value,
|
def render_with_possible_json_value(hass, template, value,
|
||||||
error_value=_SENTINEL):
|
error_value=_SENTINEL):
|
||||||
"""
|
"""Render template with value exposed.
|
||||||
Renders template with value exposed.
|
|
||||||
If valid JSON will expose value_json too.
|
If valid JSON will expose value_json too.
|
||||||
"""
|
"""
|
||||||
variables = {
|
variables = {
|
||||||
@ -65,17 +63,22 @@ def render(hass, template, variables=None, **kwargs):
|
|||||||
|
|
||||||
class AllStates(object):
|
class AllStates(object):
|
||||||
"""Class to expose all HA states as attributes."""
|
"""Class to expose all HA states as attributes."""
|
||||||
|
|
||||||
def __init__(self, hass):
|
def __init__(self, hass):
|
||||||
|
"""Initialize all states."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
"""Return the domain state."""
|
||||||
return DomainStates(self._hass, name)
|
return DomainStates(self._hass, name)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
"""Return all states."""
|
||||||
return iter(sorted(self._hass.states.all(),
|
return iter(sorted(self._hass.states.all(),
|
||||||
key=lambda state: state.entity_id))
|
key=lambda state: state.entity_id))
|
||||||
|
|
||||||
def __call__(self, entity_id):
|
def __call__(self, entity_id):
|
||||||
|
"""Return the states."""
|
||||||
state = self._hass.states.get(entity_id)
|
state = self._hass.states.get(entity_id)
|
||||||
return STATE_UNKNOWN if state is None else state.state
|
return STATE_UNKNOWN if state is None else state.state
|
||||||
|
|
||||||
@ -84,13 +87,16 @@ class DomainStates(object):
|
|||||||
"""Class to expose a specific HA domain as attributes."""
|
"""Class to expose a specific HA domain as attributes."""
|
||||||
|
|
||||||
def __init__(self, hass, domain):
|
def __init__(self, hass, domain):
|
||||||
|
"""Initialize the domain states."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._domain = domain
|
self._domain = domain
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
"""Return the states."""
|
||||||
return self._hass.states.get('{}.{}'.format(self._domain, name))
|
return self._hass.states.get('{}.{}'.format(self._domain, name))
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
"""Return the iteration over all the states."""
|
||||||
return iter(sorted(
|
return iter(sorted(
|
||||||
(state for state in self._hass.states.all()
|
(state for state in self._hass.states.all()
|
||||||
if state.domain == self._domain),
|
if state.domain == self._domain),
|
||||||
@ -101,7 +107,7 @@ class LocationMethods(object):
|
|||||||
"""Class to expose distance helpers to templates."""
|
"""Class to expose distance helpers to templates."""
|
||||||
|
|
||||||
def __init__(self, hass):
|
def __init__(self, hass):
|
||||||
"""Initialize distance helpers."""
|
"""Initialize the distance helpers."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
|
|
||||||
def closest(self, *args):
|
def closest(self, *args):
|
||||||
@ -118,7 +124,6 @@ class LocationMethods(object):
|
|||||||
closest('zone.school', 'group.children')
|
closest('zone.school', 'group.children')
|
||||||
closest(states.zone.school, 'group.children')
|
closest(states.zone.school, 'group.children')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
latitude = self._hass.config.latitude
|
latitude = self._hass.config.latitude
|
||||||
longitude = self._hass.config.longitude
|
longitude = self._hass.config.longitude
|
||||||
@ -250,8 +255,7 @@ def forgiving_float(value):
|
|||||||
|
|
||||||
|
|
||||||
class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
||||||
"""Home Assistant template environment."""
|
"""The Home Assistant template environment."""
|
||||||
|
|
||||||
def is_safe_callable(self, obj):
|
def is_safe_callable(self, obj):
|
||||||
"""Test if callback is safe."""
|
"""Test if callback is safe."""
|
||||||
return isinstance(obj, AllStates) or super().is_safe_callable(obj)
|
return isinstance(obj, AllStates) or super().is_safe_callable(obj)
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.loader
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Provides methods for loading Home Assistant components.
|
Provides methods for loading Home Assistant components.
|
||||||
|
|
||||||
This module has quite some complex parts. I have tried to add as much
|
This module has quite some complex parts. I have tried to add as much
|
||||||
@ -33,7 +30,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def prepare(hass):
|
def prepare(hass):
|
||||||
""" Prepares the loading of components. """
|
"""Prepare the loading of components."""
|
||||||
global PREPARED # pylint: disable=global-statement
|
global PREPARED # pylint: disable=global-statement
|
||||||
|
|
||||||
# Load the built-in components
|
# Load the built-in components
|
||||||
@ -74,17 +71,18 @@ def prepare(hass):
|
|||||||
|
|
||||||
|
|
||||||
def set_component(comp_name, component):
|
def set_component(comp_name, component):
|
||||||
""" Sets a component in the cache. """
|
"""Set a component in the cache."""
|
||||||
_check_prepared()
|
_check_prepared()
|
||||||
|
|
||||||
_COMPONENT_CACHE[comp_name] = component
|
_COMPONENT_CACHE[comp_name] = component
|
||||||
|
|
||||||
|
|
||||||
def get_component(comp_name):
|
def get_component(comp_name):
|
||||||
""" Tries to load specified component.
|
"""Try to load specified component.
|
||||||
Looks in config dir first, then built-in components.
|
|
||||||
Only returns it if also found to be valid. """
|
|
||||||
|
|
||||||
|
Looks in config dir first, then built-in components.
|
||||||
|
Only returns it if also found to be valid.
|
||||||
|
"""
|
||||||
if comp_name in _COMPONENT_CACHE:
|
if comp_name in _COMPONENT_CACHE:
|
||||||
return _COMPONENT_CACHE[comp_name]
|
return _COMPONENT_CACHE[comp_name]
|
||||||
|
|
||||||
@ -145,14 +143,14 @@ def get_component(comp_name):
|
|||||||
|
|
||||||
|
|
||||||
def load_order_components(components):
|
def load_order_components(components):
|
||||||
"""
|
"""Take in a list of components we want to load.
|
||||||
Takes in a list of components we want to load:
|
|
||||||
- filters out components we cannot load
|
- filters out components we cannot load
|
||||||
- filters out components that have invalid/circular dependencies
|
- filters out components that have invalid/circular dependencies
|
||||||
- Will make sure the recorder component is loaded first
|
- Will make sure the recorder component is loaded first
|
||||||
- Will ensure that all components that do not directly depend on
|
- Will ensure that all components that do not directly depend on
|
||||||
the group component will be loaded before the group component.
|
the group component will be loaded before the group component.
|
||||||
- returns an OrderedSet load order.
|
- returns an OrderedSet load order.
|
||||||
"""
|
"""
|
||||||
_check_prepared()
|
_check_prepared()
|
||||||
|
|
||||||
@ -175,8 +173,8 @@ def load_order_components(components):
|
|||||||
|
|
||||||
|
|
||||||
def load_order_component(comp_name):
|
def load_order_component(comp_name):
|
||||||
"""
|
"""Return an OrderedSet of components in the correct order of loading.
|
||||||
Returns an OrderedSet of components in the correct order of loading.
|
|
||||||
Raises HomeAssistantError if a circular dependency is detected.
|
Raises HomeAssistantError if a circular dependency is detected.
|
||||||
Returns an empty list if component could not be loaded.
|
Returns an empty list if component could not be loaded.
|
||||||
"""
|
"""
|
||||||
@ -184,10 +182,10 @@ def load_order_component(comp_name):
|
|||||||
|
|
||||||
|
|
||||||
def _load_order_component(comp_name, load_order, loading):
|
def _load_order_component(comp_name, load_order, loading):
|
||||||
""" Recursive function to get load order of components. """
|
"""Recursive function to get load order of components."""
|
||||||
component = get_component(comp_name)
|
component = get_component(comp_name)
|
||||||
|
|
||||||
# if None it does not exist, error already thrown by get_component
|
# If None it does not exist, error already thrown by get_component.
|
||||||
if component is None:
|
if component is None:
|
||||||
return OrderedSet()
|
return OrderedSet()
|
||||||
|
|
||||||
@ -198,7 +196,7 @@ def _load_order_component(comp_name, load_order, loading):
|
|||||||
if dependency in load_order:
|
if dependency in load_order:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# If we are already loading it, we have a circular dependency
|
# If we are already loading it, we have a circular dependency.
|
||||||
if dependency in loading:
|
if dependency in loading:
|
||||||
_LOGGER.error('Circular dependency detected: %s -> %s',
|
_LOGGER.error('Circular dependency detected: %s -> %s',
|
||||||
comp_name, dependency)
|
comp_name, dependency)
|
||||||
@ -221,7 +219,7 @@ def _load_order_component(comp_name, load_order, loading):
|
|||||||
|
|
||||||
|
|
||||||
def _check_prepared():
|
def _check_prepared():
|
||||||
""" Issues a warning if loader.prepare() has never been called. """
|
"""Issue a warning if loader.prepare() has never been called."""
|
||||||
if not PREPARED:
|
if not PREPARED:
|
||||||
_LOGGER.warning((
|
_LOGGER.warning((
|
||||||
"You did not call loader.prepare() yet. "
|
"You did not call loader.prepare() yet. "
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.remote
|
Support for an interface to work with a remote instance of Home Assistant.
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
A module containing drop in replacements for core parts that will interface
|
|
||||||
with a remote instance of Home Assistant.
|
|
||||||
|
|
||||||
If a connection error occurs while communicating with the API a
|
If a connection error occurs while communicating with the API a
|
||||||
HomeAssistantError will be raised.
|
HomeAssistantError will be raised.
|
||||||
@ -34,23 +31,25 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class APIStatus(enum.Enum):
|
class APIStatus(enum.Enum):
|
||||||
""" Represents API status. """
|
"""Represent API status."""
|
||||||
# pylint: disable=no-init,invalid-name,too-few-public-methods
|
|
||||||
|
|
||||||
|
# pylint: disable=no-init,invalid-name,too-few-public-methods
|
||||||
OK = "ok"
|
OK = "ok"
|
||||||
INVALID_PASSWORD = "invalid_password"
|
INVALID_PASSWORD = "invalid_password"
|
||||||
CANNOT_CONNECT = "cannot_connect"
|
CANNOT_CONNECT = "cannot_connect"
|
||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
"""Return the state."""
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class API(object):
|
class API(object):
|
||||||
""" Object to pass around Home Assistant API location and credentials. """
|
"""Object to pass around Home Assistant API location and credentials."""
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
def __init__(self, host, api_password=None, port=None, use_ssl=False):
|
def __init__(self, host, api_password=None, port=None, use_ssl=False):
|
||||||
|
"""Initalize the API."""
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port or SERVER_PORT
|
self.port = port or SERVER_PORT
|
||||||
self.api_password = api_password
|
self.api_password = api_password
|
||||||
@ -65,14 +64,14 @@ class API(object):
|
|||||||
self._headers[HTTP_HEADER_HA_AUTH] = api_password
|
self._headers[HTTP_HEADER_HA_AUTH] = api_password
|
||||||
|
|
||||||
def validate_api(self, force_validate=False):
|
def validate_api(self, force_validate=False):
|
||||||
""" Tests if we can communicate with the API. """
|
"""Test if we can communicate with the API."""
|
||||||
if self.status is None or force_validate:
|
if self.status is None or force_validate:
|
||||||
self.status = validate_api(self)
|
self.status = validate_api(self)
|
||||||
|
|
||||||
return self.status == APIStatus.OK
|
return self.status == APIStatus.OK
|
||||||
|
|
||||||
def __call__(self, method, path, data=None):
|
def __call__(self, method, path, data=None):
|
||||||
""" Makes a call to the Home Assistant API. """
|
"""Make a call to the Home Assistant API."""
|
||||||
if data is not None:
|
if data is not None:
|
||||||
data = json.dumps(data, cls=JSONEncoder)
|
data = json.dumps(data, cls=JSONEncoder)
|
||||||
|
|
||||||
@ -96,15 +95,17 @@ class API(object):
|
|||||||
raise HomeAssistantError(error)
|
raise HomeAssistantError(error)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return the representation of the API."""
|
||||||
return "API({}, {}, {})".format(
|
return "API({}, {}, {})".format(
|
||||||
self.host, self.api_password, self.port)
|
self.host, self.api_password, self.port)
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistant(ha.HomeAssistant):
|
class HomeAssistant(ha.HomeAssistant):
|
||||||
""" Home Assistant that forwards work. """
|
"""Home Assistant that forwards work."""
|
||||||
# pylint: disable=super-init-not-called,too-many-instance-attributes
|
|
||||||
|
|
||||||
|
# pylint: disable=super-init-not-called,too-many-instance-attributes
|
||||||
def __init__(self, remote_api, local_api=None):
|
def __init__(self, remote_api, local_api=None):
|
||||||
|
"""Initalize the forward instance."""
|
||||||
if not remote_api.validate_api():
|
if not remote_api.validate_api():
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
"Remote API at {}:{} not valid: {}".format(
|
"Remote API at {}:{} not valid: {}".format(
|
||||||
@ -122,6 +123,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
self.config.api = local_api
|
self.config.api = local_api
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
"""Start the instance."""
|
||||||
# Ensure a local API exists to connect with remote
|
# Ensure a local API exists to connect with remote
|
||||||
if self.config.api is None:
|
if self.config.api is None:
|
||||||
if not bootstrap.setup_component(self, 'api'):
|
if not bootstrap.setup_component(self, 'api'):
|
||||||
@ -141,7 +143,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
'local api {}').format(self.remote_api, self.config.api))
|
'local api {}').format(self.remote_api, self.config.api))
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Stops Home Assistant and shuts down all threads. """
|
"""Stop Home Assistant and shuts down all threads."""
|
||||||
_LOGGER.info("Stopping")
|
_LOGGER.info("Stopping")
|
||||||
|
|
||||||
self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP,
|
self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP,
|
||||||
@ -154,16 +156,19 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
|
|
||||||
|
|
||||||
class EventBus(ha.EventBus):
|
class EventBus(ha.EventBus):
|
||||||
""" EventBus implementation that forwards fire_event to remote API. """
|
"""EventBus implementation that forwards fire_event to remote API."""
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
def __init__(self, api, pool=None):
|
def __init__(self, api, pool=None):
|
||||||
|
"""Initalize the eventbus."""
|
||||||
super().__init__(pool)
|
super().__init__(pool)
|
||||||
self._api = api
|
self._api = api
|
||||||
|
|
||||||
def fire(self, event_type, event_data=None, origin=ha.EventOrigin.local):
|
def fire(self, event_type, event_data=None, origin=ha.EventOrigin.local):
|
||||||
""" Forward local events to remote target,
|
"""Forward local events to remote target.
|
||||||
handles remote event as usual. """
|
|
||||||
|
Handles remote event as usual.
|
||||||
|
"""
|
||||||
# All local events that are not TIME_CHANGED are forwarded to API
|
# All local events that are not TIME_CHANGED are forwarded to API
|
||||||
if origin == ha.EventOrigin.local and \
|
if origin == ha.EventOrigin.local and \
|
||||||
event_type != ha.EVENT_TIME_CHANGED:
|
event_type != ha.EVENT_TIME_CHANGED:
|
||||||
@ -175,9 +180,10 @@ class EventBus(ha.EventBus):
|
|||||||
|
|
||||||
|
|
||||||
class EventForwarder(object):
|
class EventForwarder(object):
|
||||||
""" Listens for events and forwards to specified APIs. """
|
"""Listens for events and forwards to specified APIs."""
|
||||||
|
|
||||||
def __init__(self, hass, restrict_origin=None):
|
def __init__(self, hass, restrict_origin=None):
|
||||||
|
"""Initalize the event forwarder."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.restrict_origin = restrict_origin
|
self.restrict_origin = restrict_origin
|
||||||
|
|
||||||
@ -188,8 +194,7 @@ class EventForwarder(object):
|
|||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
def connect(self, api):
|
def connect(self, api):
|
||||||
"""
|
"""Attach to a Home Assistant instance and forward events.
|
||||||
Attach to a Home Assistant instance and forward events.
|
|
||||||
|
|
||||||
Will overwrite old target if one exists with same host/port.
|
Will overwrite old target if one exists with same host/port.
|
||||||
"""
|
"""
|
||||||
@ -203,7 +208,7 @@ class EventForwarder(object):
|
|||||||
self._targets[key] = api
|
self._targets[key] = api
|
||||||
|
|
||||||
def disconnect(self, api):
|
def disconnect(self, api):
|
||||||
""" Removes target from being forwarded to. """
|
"""Remove target from being forwarded to."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
key = (api.host, api.port)
|
key = (api.host, api.port)
|
||||||
|
|
||||||
@ -217,7 +222,7 @@ class EventForwarder(object):
|
|||||||
return did_remove
|
return did_remove
|
||||||
|
|
||||||
def _event_listener(self, event):
|
def _event_listener(self, event):
|
||||||
""" Listen and forwards all events. """
|
"""Listen and forward all events."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
# We don't forward time events or, if enabled, non-local events
|
# We don't forward time events or, if enabled, non-local events
|
||||||
if event.event_type == ha.EVENT_TIME_CHANGED or \
|
if event.event_type == ha.EVENT_TIME_CHANGED or \
|
||||||
@ -229,16 +234,12 @@ class EventForwarder(object):
|
|||||||
|
|
||||||
|
|
||||||
class StateMachine(ha.StateMachine):
|
class StateMachine(ha.StateMachine):
|
||||||
"""
|
"""Fire set events to an API. Uses state_change events to track states."""
|
||||||
Fires set events to an API.
|
|
||||||
Uses state_change events to track states.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, bus, api):
|
def __init__(self, bus, api):
|
||||||
|
"""Initalize the statemachine."""
|
||||||
super().__init__(None)
|
super().__init__(None)
|
||||||
|
|
||||||
self._api = api
|
self._api = api
|
||||||
|
|
||||||
self.mirror()
|
self.mirror()
|
||||||
|
|
||||||
bus.listen(ha.EVENT_STATE_CHANGED, self._state_changed_listener)
|
bus.listen(ha.EVENT_STATE_CHANGED, self._state_changed_listener)
|
||||||
@ -251,16 +252,16 @@ class StateMachine(ha.StateMachine):
|
|||||||
return remove_state(self._api, entity_id)
|
return remove_state(self._api, entity_id)
|
||||||
|
|
||||||
def set(self, entity_id, new_state, attributes=None):
|
def set(self, entity_id, new_state, attributes=None):
|
||||||
""" Calls set_state on remote API . """
|
"""Call set_state on remote API."""
|
||||||
set_state(self._api, entity_id, new_state, attributes)
|
set_state(self._api, entity_id, new_state, attributes)
|
||||||
|
|
||||||
def mirror(self):
|
def mirror(self):
|
||||||
""" Discards current data and mirrors the remote state machine. """
|
"""Discard current data and mirrors the remote state machine."""
|
||||||
self._states = {state.entity_id: state for state
|
self._states = {state.entity_id: state for state
|
||||||
in get_states(self._api)}
|
in get_states(self._api)}
|
||||||
|
|
||||||
def _state_changed_listener(self, event):
|
def _state_changed_listener(self, event):
|
||||||
""" Listens for state changed events and applies them. """
|
"""Listen for state changed events and applies them."""
|
||||||
if event.data['new_state'] is None:
|
if event.data['new_state'] is None:
|
||||||
self._states.pop(event.data['entity_id'], None)
|
self._states.pop(event.data['entity_id'], None)
|
||||||
else:
|
else:
|
||||||
@ -268,12 +269,14 @@ class StateMachine(ha.StateMachine):
|
|||||||
|
|
||||||
|
|
||||||
class JSONEncoder(json.JSONEncoder):
|
class JSONEncoder(json.JSONEncoder):
|
||||||
""" JSONEncoder that supports Home Assistant objects. """
|
"""JSONEncoder that supports Home Assistant objects."""
|
||||||
# pylint: disable=too-few-public-methods,method-hidden
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods,method-hidden
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
""" Converts Home Assistant objects and hands
|
"""Convert Home Assistant objects.
|
||||||
other objects to the original method. """
|
|
||||||
|
Hand other objects to the original method.
|
||||||
|
"""
|
||||||
if hasattr(obj, 'as_dict'):
|
if hasattr(obj, 'as_dict'):
|
||||||
return obj.as_dict()
|
return obj.as_dict()
|
||||||
|
|
||||||
@ -291,7 +294,7 @@ class JSONEncoder(json.JSONEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def validate_api(api):
|
def validate_api(api):
|
||||||
""" Makes a call to validate API. """
|
"""Make a call to validate API."""
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_GET, URL_API)
|
req = api(METHOD_GET, URL_API)
|
||||||
|
|
||||||
@ -309,8 +312,7 @@ def validate_api(api):
|
|||||||
|
|
||||||
|
|
||||||
def connect_remote_events(from_api, to_api):
|
def connect_remote_events(from_api, to_api):
|
||||||
""" Sets up from_api to forward all events to to_api. """
|
"""Setup from_api to forward all events to to_api."""
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'host': to_api.host,
|
'host': to_api.host,
|
||||||
'api_password': to_api.api_password,
|
'api_password': to_api.api_password,
|
||||||
@ -335,7 +337,7 @@ def connect_remote_events(from_api, to_api):
|
|||||||
|
|
||||||
|
|
||||||
def disconnect_remote_events(from_api, to_api):
|
def disconnect_remote_events(from_api, to_api):
|
||||||
""" Disconnects forwarding events from from_api to to_api. """
|
"""Disconnect forwarding events from from_api to to_api."""
|
||||||
data = {
|
data = {
|
||||||
'host': to_api.host,
|
'host': to_api.host,
|
||||||
'port': to_api.port
|
'port': to_api.port
|
||||||
@ -359,7 +361,7 @@ def disconnect_remote_events(from_api, to_api):
|
|||||||
|
|
||||||
|
|
||||||
def get_event_listeners(api):
|
def get_event_listeners(api):
|
||||||
""" List of events that is being listened for. """
|
"""List of events that is being listened for."""
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_GET, URL_API_EVENTS)
|
req = api(METHOD_GET, URL_API_EVENTS)
|
||||||
|
|
||||||
@ -373,8 +375,7 @@ def get_event_listeners(api):
|
|||||||
|
|
||||||
|
|
||||||
def fire_event(api, event_type, data=None):
|
def fire_event(api, event_type, data=None):
|
||||||
""" Fire an event at remote API. """
|
"""Fire an event at remote API."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_POST, URL_API_EVENTS_EVENT.format(event_type), data)
|
req = api(METHOD_POST, URL_API_EVENTS_EVENT.format(event_type), data)
|
||||||
|
|
||||||
@ -387,8 +388,7 @@ def fire_event(api, event_type, data=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_state(api, entity_id):
|
def get_state(api, entity_id):
|
||||||
""" Queries given API for state of entity_id. """
|
"""Query given API for state of entity_id."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_GET, URL_API_STATES_ENTITY.format(entity_id))
|
req = api(METHOD_GET, URL_API_STATES_ENTITY.format(entity_id))
|
||||||
|
|
||||||
@ -405,8 +405,7 @@ def get_state(api, entity_id):
|
|||||||
|
|
||||||
|
|
||||||
def get_states(api):
|
def get_states(api):
|
||||||
""" Queries given API for all states. """
|
"""Query given API for all states."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_GET,
|
req = api(METHOD_GET,
|
||||||
URL_API_STATES)
|
URL_API_STATES)
|
||||||
@ -424,7 +423,7 @@ def get_states(api):
|
|||||||
def remove_state(api, entity_id):
|
def remove_state(api, entity_id):
|
||||||
"""Call API to remove state for entity_id.
|
"""Call API to remove state for entity_id.
|
||||||
|
|
||||||
Returns True if entity is gone (removed/never existed).
|
Return True if entity is gone (removed/never existed).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_DELETE, URL_API_STATES_ENTITY.format(entity_id))
|
req = api(METHOD_DELETE, URL_API_STATES_ENTITY.format(entity_id))
|
||||||
@ -442,11 +441,10 @@ def remove_state(api, entity_id):
|
|||||||
|
|
||||||
|
|
||||||
def set_state(api, entity_id, new_state, attributes=None):
|
def set_state(api, entity_id, new_state, attributes=None):
|
||||||
"""
|
"""Tell API to update state for entity_id.
|
||||||
Tells API to update state for entity_id.
|
|
||||||
Returns True if success.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
Return True if success.
|
||||||
|
"""
|
||||||
attributes = attributes or {}
|
attributes = attributes or {}
|
||||||
|
|
||||||
data = {'state': new_state,
|
data = {'state': new_state,
|
||||||
@ -471,16 +469,16 @@ def set_state(api, entity_id, new_state, attributes=None):
|
|||||||
|
|
||||||
|
|
||||||
def is_state(api, entity_id, state):
|
def is_state(api, entity_id, state):
|
||||||
""" Queries API to see if entity_id is specified state. """
|
"""Query API to see if entity_id is specified state."""
|
||||||
cur_state = get_state(api, entity_id)
|
cur_state = get_state(api, entity_id)
|
||||||
|
|
||||||
return cur_state and cur_state.state == state
|
return cur_state and cur_state.state == state
|
||||||
|
|
||||||
|
|
||||||
def get_services(api):
|
def get_services(api):
|
||||||
"""
|
"""Return a list of dicts.
|
||||||
Returns a list of dicts. Each dict has a string "domain" and
|
|
||||||
a list of strings "services".
|
Each dict has a string "domain" and a list of strings "services".
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_GET, URL_API_SERVICES)
|
req = api(METHOD_GET, URL_API_SERVICES)
|
||||||
@ -495,7 +493,7 @@ def get_services(api):
|
|||||||
|
|
||||||
|
|
||||||
def call_service(api, domain, service, service_data=None):
|
def call_service(api, domain, service, service_data=None):
|
||||||
""" Calls a service at the remote API. """
|
"""Call a service at the remote API."""
|
||||||
try:
|
try:
|
||||||
req = api(METHOD_POST,
|
req = api(METHOD_POST,
|
||||||
URL_API_SERVICES_SERVICE.format(domain, service),
|
URL_API_SERVICES_SERVICE.format(domain, service),
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
"""
|
"""Helper methods for various modules."""
|
||||||
homeassistant.util
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Helper methods for various modules.
|
|
||||||
"""
|
|
||||||
import collections
|
import collections
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import threading
|
import threading
|
||||||
@ -25,24 +20,24 @@ RE_SLUGIFY = re.compile(r'[^a-z0-9_]+')
|
|||||||
|
|
||||||
|
|
||||||
def sanitize_filename(filename):
|
def sanitize_filename(filename):
|
||||||
""" Sanitizes a filename by removing .. / and \\. """
|
r"""Sanitize a filename by removing .. / and \\."""
|
||||||
return RE_SANITIZE_FILENAME.sub("", filename)
|
return RE_SANITIZE_FILENAME.sub("", filename)
|
||||||
|
|
||||||
|
|
||||||
def sanitize_path(path):
|
def sanitize_path(path):
|
||||||
""" Sanitizes a path by removing ~ and .. """
|
"""Sanitize a path by removing ~ and .."""
|
||||||
return RE_SANITIZE_PATH.sub("", path)
|
return RE_SANITIZE_PATH.sub("", path)
|
||||||
|
|
||||||
|
|
||||||
def slugify(text):
|
def slugify(text):
|
||||||
""" Slugifies a given text. """
|
"""Slugify a given text."""
|
||||||
text = text.lower().replace(" ", "_")
|
text = text.lower().replace(" ", "_")
|
||||||
|
|
||||||
return RE_SLUGIFY.sub("", text)
|
return RE_SLUGIFY.sub("", text)
|
||||||
|
|
||||||
|
|
||||||
def repr_helper(inp):
|
def repr_helper(inp):
|
||||||
""" Helps creating a more readable string representation of objects. """
|
"""Help creating a more readable string representation of objects."""
|
||||||
if isinstance(inp, (dict, MappingProxyType)):
|
if isinstance(inp, (dict, MappingProxyType)):
|
||||||
return ", ".join(
|
return ", ".join(
|
||||||
repr_helper(key)+"="+repr_helper(item) for key, item
|
repr_helper(key)+"="+repr_helper(item) for key, item
|
||||||
@ -54,7 +49,7 @@ def repr_helper(inp):
|
|||||||
|
|
||||||
|
|
||||||
def convert(value, to_type, default=None):
|
def convert(value, to_type, default=None):
|
||||||
""" Converts value to to_type, returns default if fails. """
|
"""Convert value to to_type, returns default if fails."""
|
||||||
try:
|
try:
|
||||||
return default if value is None else to_type(value)
|
return default if value is None else to_type(value)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
@ -63,8 +58,10 @@ def convert(value, to_type, default=None):
|
|||||||
|
|
||||||
|
|
||||||
def ensure_unique_string(preferred_string, current_strings):
|
def ensure_unique_string(preferred_string, current_strings):
|
||||||
""" Returns a string that is not present in current_strings.
|
"""Return a string that is not present in current_strings.
|
||||||
If preferred string exists will append _2, _3, .. """
|
|
||||||
|
If preferred string exists will append _2, _3, ..
|
||||||
|
"""
|
||||||
test_string = preferred_string
|
test_string = preferred_string
|
||||||
current_strings = set(current_strings)
|
current_strings = set(current_strings)
|
||||||
|
|
||||||
@ -79,7 +76,7 @@ def ensure_unique_string(preferred_string, current_strings):
|
|||||||
|
|
||||||
# Taken from: http://stackoverflow.com/a/11735897
|
# Taken from: http://stackoverflow.com/a/11735897
|
||||||
def get_local_ip():
|
def get_local_ip():
|
||||||
""" Tries to determine the local IP address of the machine. """
|
"""Try to determine the local IP address of the machine."""
|
||||||
try:
|
try:
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
@ -95,7 +92,7 @@ def get_local_ip():
|
|||||||
|
|
||||||
# Taken from http://stackoverflow.com/a/23728630
|
# Taken from http://stackoverflow.com/a/23728630
|
||||||
def get_random_string(length=10):
|
def get_random_string(length=10):
|
||||||
""" Returns a random string with letters and digits. """
|
"""Return a random string with letters and digits."""
|
||||||
generator = random.SystemRandom()
|
generator = random.SystemRandom()
|
||||||
source_chars = string.ascii_letters + string.digits
|
source_chars = string.ascii_letters + string.digits
|
||||||
|
|
||||||
@ -103,34 +100,39 @@ def get_random_string(length=10):
|
|||||||
|
|
||||||
|
|
||||||
class OrderedEnum(enum.Enum):
|
class OrderedEnum(enum.Enum):
|
||||||
""" Taken from Python 3.4.0 docs. """
|
"""Taken from Python 3.4.0 docs."""
|
||||||
# pylint: disable=no-init, too-few-public-methods
|
|
||||||
|
|
||||||
|
# pylint: disable=no-init, too-few-public-methods
|
||||||
def __ge__(self, other):
|
def __ge__(self, other):
|
||||||
|
"""Return the greater than element."""
|
||||||
if self.__class__ is other.__class__:
|
if self.__class__ is other.__class__:
|
||||||
return self.value >= other.value
|
return self.value >= other.value
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __gt__(self, other):
|
def __gt__(self, other):
|
||||||
|
"""Return the greater element."""
|
||||||
if self.__class__ is other.__class__:
|
if self.__class__ is other.__class__:
|
||||||
return self.value > other.value
|
return self.value > other.value
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other):
|
||||||
|
"""Return the lower than element."""
|
||||||
if self.__class__ is other.__class__:
|
if self.__class__ is other.__class__:
|
||||||
return self.value <= other.value
|
return self.value <= other.value
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
|
"""Return the lower element."""
|
||||||
if self.__class__ is other.__class__:
|
if self.__class__ is other.__class__:
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
class OrderedSet(collections.MutableSet):
|
class OrderedSet(collections.MutableSet):
|
||||||
""" Ordered set taken from http://code.activestate.com/recipes/576694/ """
|
"""Ordered set taken from http://code.activestate.com/recipes/576694/."""
|
||||||
|
|
||||||
def __init__(self, iterable=None):
|
def __init__(self, iterable=None):
|
||||||
|
"""Initialize the set."""
|
||||||
self.end = end = []
|
self.end = end = []
|
||||||
end += [None, end, end] # sentinel node for doubly linked list
|
end += [None, end, end] # sentinel node for doubly linked list
|
||||||
self.map = {} # key --> [key, prev, next]
|
self.map = {} # key --> [key, prev, next]
|
||||||
@ -138,20 +140,22 @@ class OrderedSet(collections.MutableSet):
|
|||||||
self |= iterable
|
self |= iterable
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
"""Return the length of the set."""
|
||||||
return len(self.map)
|
return len(self.map)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
|
"""Check if key is in set."""
|
||||||
return key in self.map
|
return key in self.map
|
||||||
|
|
||||||
def add(self, key):
|
def add(self, key):
|
||||||
""" Add an element to the end of the set. """
|
"""Add an element to the end of the set."""
|
||||||
if key not in self.map:
|
if key not in self.map:
|
||||||
end = self.end
|
end = self.end
|
||||||
curr = end[1]
|
curr = end[1]
|
||||||
curr[2] = end[1] = self.map[key] = [key, curr, end]
|
curr[2] = end[1] = self.map[key] = [key, curr, end]
|
||||||
|
|
||||||
def promote(self, key):
|
def promote(self, key):
|
||||||
""" Promote element to beginning of the set, add if not there. """
|
"""Promote element to beginning of the set, add if not there."""
|
||||||
if key in self.map:
|
if key in self.map:
|
||||||
self.discard(key)
|
self.discard(key)
|
||||||
|
|
||||||
@ -160,13 +164,14 @@ class OrderedSet(collections.MutableSet):
|
|||||||
curr[2] = begin[1] = self.map[key] = [key, curr, begin]
|
curr[2] = begin[1] = self.map[key] = [key, curr, begin]
|
||||||
|
|
||||||
def discard(self, key):
|
def discard(self, key):
|
||||||
""" Discard an element from the set. """
|
"""Discard an element from the set."""
|
||||||
if key in self.map:
|
if key in self.map:
|
||||||
key, prev_item, next_item = self.map.pop(key)
|
key, prev_item, next_item = self.map.pop(key)
|
||||||
prev_item[2] = next_item
|
prev_item[2] = next_item
|
||||||
next_item[1] = prev_item
|
next_item[1] = prev_item
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
"""Iteration of the set."""
|
||||||
end = self.end
|
end = self.end
|
||||||
curr = end[2]
|
curr = end[2]
|
||||||
while curr is not end:
|
while curr is not end:
|
||||||
@ -174,6 +179,7 @@ class OrderedSet(collections.MutableSet):
|
|||||||
curr = curr[2]
|
curr = curr[2]
|
||||||
|
|
||||||
def __reversed__(self):
|
def __reversed__(self):
|
||||||
|
"""reverse the ordering."""
|
||||||
end = self.end
|
end = self.end
|
||||||
curr = end[1]
|
curr = end[1]
|
||||||
while curr is not end:
|
while curr is not end:
|
||||||
@ -181,8 +187,8 @@ class OrderedSet(collections.MutableSet):
|
|||||||
curr = curr[1]
|
curr = curr[1]
|
||||||
|
|
||||||
def pop(self, last=True): # pylint: disable=arguments-differ
|
def pop(self, last=True): # pylint: disable=arguments-differ
|
||||||
"""
|
"""Pop element of the end of the set.
|
||||||
Pops element of the end of the set.
|
|
||||||
Set last=False to pop from the beginning.
|
Set last=False to pop from the beginning.
|
||||||
"""
|
"""
|
||||||
if not self:
|
if not self:
|
||||||
@ -192,24 +198,27 @@ class OrderedSet(collections.MutableSet):
|
|||||||
return key
|
return key
|
||||||
|
|
||||||
def update(self, *args):
|
def update(self, *args):
|
||||||
""" Add elements from args to the set. """
|
"""Add elements from args to the set."""
|
||||||
for item in chain(*args):
|
for item in chain(*args):
|
||||||
self.add(item)
|
self.add(item)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return the representation."""
|
||||||
if not self:
|
if not self:
|
||||||
return '%s()' % (self.__class__.__name__,)
|
return '%s()' % (self.__class__.__name__,)
|
||||||
return '%s(%r)' % (self.__class__.__name__, list(self))
|
return '%s(%r)' % (self.__class__.__name__, list(self))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
"""Return the comparision."""
|
||||||
if isinstance(other, OrderedSet):
|
if isinstance(other, OrderedSet):
|
||||||
return len(self) == len(other) and list(self) == list(other)
|
return len(self) == len(other) and list(self) == list(other)
|
||||||
return set(self) == set(other)
|
return set(self) == set(other)
|
||||||
|
|
||||||
|
|
||||||
class Throttle(object):
|
class Throttle(object):
|
||||||
"""
|
"""A class for throttling the execution of tasks.
|
||||||
A method decorator to add a cooldown to a method to prevent it from being
|
|
||||||
|
This method decorator adds a cooldown to a method to prevent it from being
|
||||||
called more then 1 time within the timedelta interval `min_time` after it
|
called more then 1 time within the timedelta interval `min_time` after it
|
||||||
returned its result.
|
returned its result.
|
||||||
|
|
||||||
@ -223,13 +232,15 @@ class Throttle(object):
|
|||||||
|
|
||||||
Adds a datetime attribute `last_call` to the method.
|
Adds a datetime attribute `last_call` to the method.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
def __init__(self, min_time, limit_no_throttle=None):
|
def __init__(self, min_time, limit_no_throttle=None):
|
||||||
|
"""Initialize the throttle."""
|
||||||
self.min_time = min_time
|
self.min_time = min_time
|
||||||
self.limit_no_throttle = limit_no_throttle
|
self.limit_no_throttle = limit_no_throttle
|
||||||
|
|
||||||
def __call__(self, method):
|
def __call__(self, method):
|
||||||
|
"""Caller for the throttle."""
|
||||||
if self.limit_no_throttle is not None:
|
if self.limit_no_throttle is not None:
|
||||||
method = Throttle(self.limit_no_throttle)(method)
|
method = Throttle(self.limit_no_throttle)(method)
|
||||||
|
|
||||||
@ -248,8 +259,8 @@ class Throttle(object):
|
|||||||
|
|
||||||
@wraps(method)
|
@wraps(method)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
"""
|
"""Wrapper that allows wrapped to be called only once per min_time.
|
||||||
Wrapper that allows wrapped to be called only once per min_time.
|
|
||||||
If we cannot acquire the lock, it is running so return None.
|
If we cannot acquire the lock, it is running so return None.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
@ -288,10 +299,11 @@ class Throttle(object):
|
|||||||
|
|
||||||
class ThreadPool(object):
|
class ThreadPool(object):
|
||||||
"""A priority queue-based thread pool."""
|
"""A priority queue-based thread pool."""
|
||||||
# pylint: disable=too-many-instance-attributes
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
def __init__(self, job_handler, worker_count=0, busy_callback=None):
|
def __init__(self, job_handler, worker_count=0, busy_callback=None):
|
||||||
"""
|
"""Initialize the pool.
|
||||||
|
|
||||||
job_handler: method to be called from worker thread to handle job
|
job_handler: method to be called from worker thread to handle job
|
||||||
worker_count: number of threads to run that handle jobs
|
worker_count: number of threads to run that handle jobs
|
||||||
busy_callback: method to be called when queue gets too big.
|
busy_callback: method to be called when queue gets too big.
|
||||||
@ -338,18 +350,18 @@ class ThreadPool(object):
|
|||||||
self.busy_warning_limit = self.worker_count * 3
|
self.busy_warning_limit = self.worker_count * 3
|
||||||
|
|
||||||
def add_job(self, priority, job):
|
def add_job(self, priority, job):
|
||||||
""" Add a job to the queue. """
|
"""Add a job to the queue."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if not self.running:
|
if not self.running:
|
||||||
raise RuntimeError("ThreadPool not running")
|
raise RuntimeError("ThreadPool not running")
|
||||||
|
|
||||||
self._work_queue.put(PriorityQueueItem(priority, job))
|
self._work_queue.put(PriorityQueueItem(priority, job))
|
||||||
|
|
||||||
# check if our queue is getting too big
|
# Check if our queue is getting too big.
|
||||||
if self._work_queue.qsize() > self.busy_warning_limit \
|
if self._work_queue.qsize() > self.busy_warning_limit \
|
||||||
and self._busy_callback is not None:
|
and self._busy_callback is not None:
|
||||||
|
|
||||||
# Increase limit we will issue next warning
|
# Increase limit we will issue next warning.
|
||||||
self.busy_warning_limit *= 2
|
self.busy_warning_limit *= 2
|
||||||
|
|
||||||
self._busy_callback(
|
self._busy_callback(
|
||||||
@ -404,12 +416,14 @@ class ThreadPool(object):
|
|||||||
|
|
||||||
|
|
||||||
class PriorityQueueItem(object):
|
class PriorityQueueItem(object):
|
||||||
""" Holds a priority and a value. Used within PriorityQueue. """
|
"""Holds a priority and a value. Used within PriorityQueue."""
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
def __init__(self, priority, item):
|
def __init__(self, priority, item):
|
||||||
|
"""Initialize the queue."""
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
|
"""Return the ordering."""
|
||||||
return self.priority < other.priority
|
return self.priority < other.priority
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
"""
|
"""Provides helper methods to handle the time in HA."""
|
||||||
homeassistant.util.dt
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Provides helper methods to handle the time in HA.
|
|
||||||
|
|
||||||
"""
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
@ -16,7 +10,7 @@ UTC = DEFAULT_TIME_ZONE = pytz.utc
|
|||||||
|
|
||||||
|
|
||||||
def set_default_time_zone(time_zone):
|
def set_default_time_zone(time_zone):
|
||||||
""" Sets a default time zone to be used when none is specified. """
|
"""Set a default time zone to be used when none is specified."""
|
||||||
global DEFAULT_TIME_ZONE # pylint: disable=global-statement
|
global DEFAULT_TIME_ZONE # pylint: disable=global-statement
|
||||||
|
|
||||||
assert isinstance(time_zone, dt.tzinfo)
|
assert isinstance(time_zone, dt.tzinfo)
|
||||||
@ -25,7 +19,7 @@ def set_default_time_zone(time_zone):
|
|||||||
|
|
||||||
|
|
||||||
def get_time_zone(time_zone_str):
|
def get_time_zone(time_zone_str):
|
||||||
""" Get time zone from string. Return None if unable to determine. """
|
"""Get time zone from string. Return None if unable to determine."""
|
||||||
try:
|
try:
|
||||||
return pytz.timezone(time_zone_str)
|
return pytz.timezone(time_zone_str)
|
||||||
except pytz.exceptions.UnknownTimeZoneError:
|
except pytz.exceptions.UnknownTimeZoneError:
|
||||||
@ -33,18 +27,20 @@ def get_time_zone(time_zone_str):
|
|||||||
|
|
||||||
|
|
||||||
def utcnow():
|
def utcnow():
|
||||||
""" Get now in UTC time. """
|
"""Get now in UTC time."""
|
||||||
return dt.datetime.now(UTC)
|
return dt.datetime.now(UTC)
|
||||||
|
|
||||||
|
|
||||||
def now(time_zone=None):
|
def now(time_zone=None):
|
||||||
""" Get now in specified time zone. """
|
"""Get now in specified time zone."""
|
||||||
return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE)
|
return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
def as_utc(dattim):
|
def as_utc(dattim):
|
||||||
""" Return a datetime as UTC time.
|
"""Return a datetime as UTC time.
|
||||||
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE. """
|
|
||||||
|
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE.
|
||||||
|
"""
|
||||||
if dattim.tzinfo == UTC:
|
if dattim.tzinfo == UTC:
|
||||||
return dattim
|
return dattim
|
||||||
elif dattim.tzinfo is None:
|
elif dattim.tzinfo is None:
|
||||||
@ -54,7 +50,7 @@ def as_utc(dattim):
|
|||||||
|
|
||||||
|
|
||||||
def as_local(dattim):
|
def as_local(dattim):
|
||||||
""" Converts a UTC datetime object to local time_zone. """
|
"""Convert a UTC datetime object to local time zone."""
|
||||||
if dattim.tzinfo == DEFAULT_TIME_ZONE:
|
if dattim.tzinfo == DEFAULT_TIME_ZONE:
|
||||||
return dattim
|
return dattim
|
||||||
elif dattim.tzinfo is None:
|
elif dattim.tzinfo is None:
|
||||||
@ -64,12 +60,12 @@ def as_local(dattim):
|
|||||||
|
|
||||||
|
|
||||||
def utc_from_timestamp(timestamp):
|
def utc_from_timestamp(timestamp):
|
||||||
""" Returns a UTC time from a timestamp. """
|
"""Return a UTC time from a timestamp."""
|
||||||
return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC)
|
return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC)
|
||||||
|
|
||||||
|
|
||||||
def start_of_local_day(dt_or_d=None):
|
def start_of_local_day(dt_or_d=None):
|
||||||
""" Return local datetime object of start of day from date or datetime. """
|
"""Return local datetime object of start of day from date or datetime."""
|
||||||
if dt_or_d is None:
|
if dt_or_d is None:
|
||||||
dt_or_d = now().date()
|
dt_or_d = now().date()
|
||||||
elif isinstance(dt_or_d, dt.datetime):
|
elif isinstance(dt_or_d, dt.datetime):
|
||||||
@ -80,12 +76,12 @@ def start_of_local_day(dt_or_d=None):
|
|||||||
|
|
||||||
|
|
||||||
def datetime_to_local_str(dattim):
|
def datetime_to_local_str(dattim):
|
||||||
""" Converts datetime to specified time_zone and returns a string. """
|
"""Convert datetime to specified time_zone and returns a string."""
|
||||||
return datetime_to_str(as_local(dattim))
|
return datetime_to_str(as_local(dattim))
|
||||||
|
|
||||||
|
|
||||||
def datetime_to_str(dattim):
|
def datetime_to_str(dattim):
|
||||||
""" Converts datetime to a string format.
|
"""Convert datetime to a string format.
|
||||||
|
|
||||||
@rtype : str
|
@rtype : str
|
||||||
"""
|
"""
|
||||||
@ -93,7 +89,7 @@ def datetime_to_str(dattim):
|
|||||||
|
|
||||||
|
|
||||||
def datetime_to_time_str(dattim):
|
def datetime_to_time_str(dattim):
|
||||||
""" Converts datetime to a string containing only the time.
|
"""Convert datetime to a string containing only the time.
|
||||||
|
|
||||||
@rtype : str
|
@rtype : str
|
||||||
"""
|
"""
|
||||||
@ -101,7 +97,7 @@ def datetime_to_time_str(dattim):
|
|||||||
|
|
||||||
|
|
||||||
def datetime_to_date_str(dattim):
|
def datetime_to_date_str(dattim):
|
||||||
""" Converts datetime to a string containing only the date.
|
"""Convert datetime to a string containing only the date.
|
||||||
|
|
||||||
@rtype : str
|
@rtype : str
|
||||||
"""
|
"""
|
||||||
@ -109,7 +105,7 @@ def datetime_to_date_str(dattim):
|
|||||||
|
|
||||||
|
|
||||||
def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
|
def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
|
||||||
""" Converts a string to a UTC datetime object.
|
"""Convert a string to a UTC datetime object.
|
||||||
|
|
||||||
@rtype: datetime
|
@rtype: datetime
|
||||||
"""
|
"""
|
||||||
@ -121,7 +117,7 @@ def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
|
|||||||
|
|
||||||
|
|
||||||
def date_str_to_date(dt_str):
|
def date_str_to_date(dt_str):
|
||||||
""" Converts a date string to a date object. """
|
"""Convert a date string to a date object."""
|
||||||
try:
|
try:
|
||||||
return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date()
|
return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date()
|
||||||
except ValueError: # If dt_str did not match our format
|
except ValueError: # If dt_str did not match our format
|
||||||
@ -129,13 +125,14 @@ def date_str_to_date(dt_str):
|
|||||||
|
|
||||||
|
|
||||||
def strip_microseconds(dattim):
|
def strip_microseconds(dattim):
|
||||||
""" Returns a copy of dattime object but with microsecond set to 0. """
|
"""Return a copy of dattime object but with microsecond set to 0."""
|
||||||
return dattim.replace(microsecond=0)
|
return dattim.replace(microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
def parse_time_str(time_str):
|
def parse_time_str(time_str):
|
||||||
""" Parse a time string (00:20:00) into Time object.
|
"""Parse a time string (00:20:00) into Time object.
|
||||||
Return None if invalid.
|
|
||||||
|
Return None if invalid.
|
||||||
"""
|
"""
|
||||||
parts = str(time_str).split(':')
|
parts = str(time_str).split(':')
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
"""
|
"""Helpers to install PyPi packages."""
|
||||||
homeassistant.util.package
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Helpers to install PyPi packages.
|
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -17,8 +13,8 @@ INSTALL_LOCK = threading.Lock()
|
|||||||
|
|
||||||
|
|
||||||
def install_package(package, upgrade=True, target=None):
|
def install_package(package, upgrade=True, target=None):
|
||||||
"""
|
"""Install a package on PyPi. Accepts pip compatible package strings.
|
||||||
Install a package on PyPi. Accepts pip compatible package strings.
|
|
||||||
Return boolean if install successful.
|
Return boolean if install successful.
|
||||||
"""
|
"""
|
||||||
# Not using 'import pip; pip.main([])' because it breaks the logger
|
# Not using 'import pip; pip.main([])' because it breaks the logger
|
||||||
@ -41,8 +37,8 @@ def install_package(package, upgrade=True, target=None):
|
|||||||
|
|
||||||
|
|
||||||
def check_package_exists(package, lib_dir):
|
def check_package_exists(package, lib_dir):
|
||||||
"""
|
"""Check if a package is installed globally or in lib_dir.
|
||||||
Check if a package is installed globally or in lib_dir.
|
|
||||||
Returns True when the requirement is met.
|
Returns True when the requirement is met.
|
||||||
Returns False when the package is not installed or doesn't meet req.
|
Returns False when the package is not installed or doesn't meet req.
|
||||||
"""
|
"""
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
"""
|
"""Temperature util functions."""
|
||||||
homeassistant.util.temperature
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Temperature util functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def fahrenheit_to_celcius(fahrenheit):
|
def fahrenheit_to_celcius(fahrenheit):
|
||||||
""" Convert a Fahrenheit temperature to Celcius. """
|
"""Convert a Fahrenheit temperature to Celsius."""
|
||||||
return (fahrenheit - 32.0) / 1.8
|
return (fahrenheit - 32.0) / 1.8
|
||||||
|
|
||||||
|
|
||||||
def celcius_to_fahrenheit(celcius):
|
def celcius_to_fahrenheit(celcius):
|
||||||
""" Convert a Celcius temperature to Fahrenheit. """
|
"""Convert a Celsius temperature to Fahrenheit."""
|
||||||
return celcius * 1.8 + 32.0
|
return celcius * 1.8 + 32.0
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""YAML utility functions."""
|
||||||
YAML utility functions.
|
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@ -26,8 +24,7 @@ def load_yaml(fname):
|
|||||||
|
|
||||||
|
|
||||||
def _include_yaml(loader, node):
|
def _include_yaml(loader, node):
|
||||||
"""
|
"""Load another YAML file and embeds it using the !include tag.
|
||||||
Loads another YAML file and embeds it using the !include tag.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
device_tracker: !include device_tracker.yaml
|
device_tracker: !include device_tracker.yaml
|
||||||
@ -37,9 +34,7 @@ def _include_yaml(loader, node):
|
|||||||
|
|
||||||
|
|
||||||
def _ordered_dict(loader, node):
|
def _ordered_dict(loader, node):
|
||||||
"""
|
"""Load YAML mappings into an ordered dict to preserve key order."""
|
||||||
Loads YAML mappings into an ordered dict to preserve key order.
|
|
||||||
"""
|
|
||||||
loader.flatten_mapping(node)
|
loader.flatten_mapping(node)
|
||||||
return OrderedDict(loader.construct_pairs(node))
|
return OrderedDict(loader.construct_pairs(node))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user