mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Merge remote-tracking branch 'origin/dev'
Conflicts: Dockerfile homeassistant/components/frontend/version.py homeassistant/components/frontend/www_static/frontend.html
This commit is contained in:
commit
74e4b024c0
@ -30,11 +30,13 @@ omit =
|
|||||||
|
|
||||||
homeassistant/components/browser.py
|
homeassistant/components/browser.py
|
||||||
homeassistant/components/camera/*
|
homeassistant/components/camera/*
|
||||||
|
homeassistant/components/device_tracker/actiontec.py
|
||||||
homeassistant/components/device_tracker/asuswrt.py
|
homeassistant/components/device_tracker/asuswrt.py
|
||||||
homeassistant/components/device_tracker/ddwrt.py
|
homeassistant/components/device_tracker/ddwrt.py
|
||||||
homeassistant/components/device_tracker/luci.py
|
homeassistant/components/device_tracker/luci.py
|
||||||
homeassistant/components/device_tracker/netgear.py
|
homeassistant/components/device_tracker/netgear.py
|
||||||
homeassistant/components/device_tracker/nmap_tracker.py
|
homeassistant/components/device_tracker/nmap_tracker.py
|
||||||
|
homeassistant/components/device_tracker/thomson.py
|
||||||
homeassistant/components/device_tracker/tomato.py
|
homeassistant/components/device_tracker/tomato.py
|
||||||
homeassistant/components/device_tracker/tplink.py
|
homeassistant/components/device_tracker/tplink.py
|
||||||
homeassistant/components/discovery.py
|
homeassistant/components/discovery.py
|
||||||
@ -56,11 +58,13 @@ omit =
|
|||||||
homeassistant/components/notify/syslog.py
|
homeassistant/components/notify/syslog.py
|
||||||
homeassistant/components/notify/xmpp.py
|
homeassistant/components/notify/xmpp.py
|
||||||
homeassistant/components/sensor/bitcoin.py
|
homeassistant/components/sensor/bitcoin.py
|
||||||
|
homeassistant/components/sensor/dht.py
|
||||||
homeassistant/components/sensor/efergy.py
|
homeassistant/components/sensor/efergy.py
|
||||||
homeassistant/components/sensor/forecast.py
|
homeassistant/components/sensor/forecast.py
|
||||||
homeassistant/components/sensor/mysensors.py
|
homeassistant/components/sensor/mysensors.py
|
||||||
homeassistant/components/sensor/openweathermap.py
|
homeassistant/components/sensor/openweathermap.py
|
||||||
homeassistant/components/sensor/rfxtrx.py
|
homeassistant/components/sensor/rfxtrx.py
|
||||||
|
homeassistant/components/sensor/rpi_gpio.py
|
||||||
homeassistant/components/sensor/sabnzbd.py
|
homeassistant/components/sensor/sabnzbd.py
|
||||||
homeassistant/components/sensor/swiss_public_transport.py
|
homeassistant/components/sensor/swiss_public_transport.py
|
||||||
homeassistant/components/sensor/systemmonitor.py
|
homeassistant/components/sensor/systemmonitor.py
|
||||||
|
@ -3,7 +3,7 @@ language: python
|
|||||||
python:
|
python:
|
||||||
- "3.4"
|
- "3.4"
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements_all.txt
|
||||||
- pip install flake8 pylint coveralls
|
- pip install flake8 pylint coveralls
|
||||||
script:
|
script:
|
||||||
- flake8 homeassistant --exclude bower_components,external
|
- flake8 homeassistant --exclude bower_components,external
|
||||||
|
@ -3,6 +3,8 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
|
|||||||
|
|
||||||
VOLUME /config
|
VOLUME /config
|
||||||
|
|
||||||
|
RUN pip3 install --no-cache-dir -r requirements_all.txt
|
||||||
|
|
||||||
#RUN apt-get update && \
|
#RUN apt-get update && \
|
||||||
# apt-get install -y cython3 libudev-dev && \
|
# apt-get install -y cython3 libudev-dev && \
|
||||||
# apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
# apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||||
|
@ -4,120 +4,60 @@ from __future__ import print_function
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
DEPENDENCIES = ['requests>=2.0', 'pyyaml>=3.11', 'pytz>=2015.2']
|
from homeassistant import bootstrap
|
||||||
IS_VIRTUAL = (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or
|
import homeassistant.config as config_util
|
||||||
hasattr(sys, 'real_prefix'))
|
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||||
|
|
||||||
|
|
||||||
def validate_python():
|
|
||||||
""" Validate we're running the right Python version. """
|
|
||||||
major, minor = sys.version_info[:2]
|
|
||||||
|
|
||||||
if major < 3 or (major == 3 and minor < 4):
|
|
||||||
print("Home Assistant requires atleast Python 3.4")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_pip():
|
|
||||||
""" Validate pip is installed so we can install packages on demand. """
|
|
||||||
if importlib.find_loader('pip') is None:
|
|
||||||
print("Your Python installation did not bundle 'pip'")
|
|
||||||
print("Home Assistant requires 'pip' to be installed.")
|
|
||||||
print("Please install pip: "
|
|
||||||
"https://pip.pypa.io/en/latest/installing.html")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
# Copy of homeassistant.util.package because we can't import yet
|
|
||||||
def install_package(package):
|
|
||||||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
|
||||||
Return boolean if install successfull."""
|
|
||||||
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
|
||||||
if not IS_VIRTUAL:
|
|
||||||
args.append('--user')
|
|
||||||
try:
|
|
||||||
return 0 == subprocess.call(args)
|
|
||||||
except subprocess.SubprocessError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def validate_dependencies():
|
|
||||||
""" Validate all dependencies that HA uses. """
|
|
||||||
ensure_pip()
|
|
||||||
|
|
||||||
print("Validating dependencies...")
|
|
||||||
import_fail = False
|
|
||||||
|
|
||||||
for requirement in DEPENDENCIES:
|
|
||||||
if not install_package(requirement):
|
|
||||||
import_fail = True
|
|
||||||
print('Fatal Error: Unable to install dependency', requirement)
|
|
||||||
|
|
||||||
if import_fail:
|
|
||||||
print(("Install dependencies by running: "
|
|
||||||
"python3 -m pip install -r requirements.txt"))
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_path_and_load_bootstrap():
|
|
||||||
""" Ensure sys load path is correct and load Home Assistant bootstrap. """
|
|
||||||
try:
|
|
||||||
from homeassistant import bootstrap
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# This is to add support to load Home Assistant using
|
|
||||||
# `python3 homeassistant` instead of `python3 -m homeassistant`
|
|
||||||
|
|
||||||
# Insert the parent directory of this file into the module search path
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
|
||||||
|
|
||||||
return bootstrap
|
|
||||||
|
|
||||||
|
|
||||||
def validate_git_submodules():
|
|
||||||
""" Validate the git submodules are cloned. """
|
|
||||||
try:
|
|
||||||
# pylint: disable=no-name-in-module, unused-variable
|
|
||||||
from homeassistant.external.noop import WORKING # noqa
|
|
||||||
except ImportError:
|
|
||||||
print("Repository submodules have not been initialized")
|
|
||||||
print("Please run: git submodule update --init --recursive")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_config_path(config_dir):
|
def ensure_config_path(config_dir):
|
||||||
""" Gets the path to the configuration file.
|
""" Validates configuration directory. """
|
||||||
Creates one if it not exists. """
|
|
||||||
|
lib_dir = os.path.join(config_dir, 'lib')
|
||||||
|
|
||||||
# Test if configuration directory exists
|
# Test if configuration directory exists
|
||||||
if not os.path.isdir(config_dir):
|
if not os.path.isdir(config_dir):
|
||||||
print(('Fatal Error: Unable to find specified configuration '
|
if config_dir != config_util.get_default_config_dir():
|
||||||
|
print(('Fatal Error: Specified configuration directory does '
|
||||||
|
'not exist {} ').format(config_dir))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir(config_dir)
|
||||||
|
except OSError:
|
||||||
|
print(('Fatal Error: Unable to create default configuration '
|
||||||
'directory {} ').format(config_dir))
|
'directory {} ').format(config_dir))
|
||||||
sys.exit()
|
sys.exit(1)
|
||||||
|
|
||||||
import homeassistant.config as config_util
|
# Test if library directory exists
|
||||||
|
if not os.path.isdir(lib_dir):
|
||||||
|
try:
|
||||||
|
os.mkdir(lib_dir)
|
||||||
|
except OSError:
|
||||||
|
print(('Fatal Error: Unable to create library '
|
||||||
|
'directory {} ').format(lib_dir))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_config_file(config_dir):
|
||||||
|
""" 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:
|
||||||
print('Error getting configuration path')
|
print('Error getting configuration path')
|
||||||
sys.exit()
|
sys.exit(1)
|
||||||
|
|
||||||
return config_path
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
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.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-c', '--config',
|
'-c', '--config',
|
||||||
metavar='path_to_config_dir',
|
metavar='path_to_config_dir',
|
||||||
default="config",
|
default=config_util.get_default_config_dir(),
|
||||||
help="Directory that contains the Home Assistant configuration")
|
help="Directory that contains the Home Assistant configuration")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--demo-mode',
|
'--demo-mode',
|
||||||
@ -133,34 +73,21 @@ def get_arguments():
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" Starts Home Assistant. """
|
""" Starts Home Assistant. """
|
||||||
validate_python()
|
|
||||||
validate_dependencies()
|
|
||||||
|
|
||||||
# Windows needs this to pick up new modules
|
|
||||||
importlib.invalidate_caches()
|
|
||||||
|
|
||||||
bootstrap = ensure_path_and_load_bootstrap()
|
|
||||||
|
|
||||||
validate_git_submodules()
|
|
||||||
|
|
||||||
args = get_arguments()
|
args = get_arguments()
|
||||||
|
|
||||||
config_dir = os.path.join(os.getcwd(), args.config)
|
config_dir = os.path.join(os.getcwd(), args.config)
|
||||||
config_path = ensure_config_path(config_dir)
|
ensure_config_path(config_dir)
|
||||||
|
|
||||||
if args.demo_mode:
|
if args.demo_mode:
|
||||||
from homeassistant.components import frontend, demo
|
|
||||||
|
|
||||||
hass = bootstrap.from_config_dict({
|
hass = bootstrap.from_config_dict({
|
||||||
frontend.DOMAIN: {},
|
'frontend': {},
|
||||||
demo.DOMAIN: {}
|
'demo': {}
|
||||||
})
|
}, config_dir=config_dir)
|
||||||
else:
|
else:
|
||||||
hass = bootstrap.from_config_file(config_path)
|
config_file = ensure_config_file(config_dir)
|
||||||
|
hass = bootstrap.from_config_file(config_file)
|
||||||
|
|
||||||
if args.open_ui:
|
if args.open_ui:
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
|
||||||
|
|
||||||
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:
|
||||||
|
@ -10,6 +10,7 @@ start by calling homeassistant.start_home_assistant(bus)
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
@ -61,13 +62,13 @@ def setup_component(hass, domain, config=None):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _handle_requirements(component, name):
|
def _handle_requirements(hass, component, name):
|
||||||
""" Installs requirements for component. """
|
""" Installs requirements for component. """
|
||||||
if not hasattr(component, 'REQUIREMENTS'):
|
if not hasattr(component, 'REQUIREMENTS'):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
for req in component.REQUIREMENTS:
|
for req in component.REQUIREMENTS:
|
||||||
if not pkg_util.install_package(req):
|
if not pkg_util.install_package(req, target=hass.config.path('lib')):
|
||||||
_LOGGER.error('Not initializing %s because could not install '
|
_LOGGER.error('Not initializing %s because could not install '
|
||||||
'dependency %s', name, req)
|
'dependency %s', name, req)
|
||||||
return False
|
return False
|
||||||
@ -88,7 +89,7 @@ def _setup_component(hass, domain, config):
|
|||||||
domain, ", ".join(missing_deps))
|
domain, ", ".join(missing_deps))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not _handle_requirements(component, domain):
|
if not _handle_requirements(hass, component, domain):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -138,14 +139,19 @@ def prepare_setup_platform(hass, config, domain, platform_name):
|
|||||||
component)
|
component)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not _handle_requirements(platform, platform_path):
|
if not _handle_requirements(hass, platform, platform_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return platform
|
return platform
|
||||||
|
|
||||||
|
|
||||||
|
def mount_local_lib_path(config_dir):
|
||||||
|
""" Add local library to Python Path """
|
||||||
|
sys.path.insert(0, os.path.join(config_dir, 'lib'))
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-statements
|
# pylint: disable=too-many-branches, too-many-statements
|
||||||
def from_config_dict(config, hass=None):
|
def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
|
||||||
"""
|
"""
|
||||||
Tries to configure Home Assistant from a config dict.
|
Tries to configure Home Assistant from a config dict.
|
||||||
|
|
||||||
@ -153,9 +159,14 @@ def from_config_dict(config, hass=None):
|
|||||||
"""
|
"""
|
||||||
if hass is None:
|
if hass is None:
|
||||||
hass = core.HomeAssistant()
|
hass = core.HomeAssistant()
|
||||||
|
if config_dir is not None:
|
||||||
|
config_dir = os.path.abspath(config_dir)
|
||||||
|
hass.config.config_dir = config_dir
|
||||||
|
mount_local_lib_path(config_dir)
|
||||||
|
|
||||||
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
|
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
|
||||||
|
|
||||||
|
if enable_log:
|
||||||
enable_logging(hass)
|
enable_logging(hass)
|
||||||
|
|
||||||
_ensure_loader_prepared(hass)
|
_ensure_loader_prepared(hass)
|
||||||
@ -195,11 +206,15 @@ def from_config_file(config_path, hass=None):
|
|||||||
hass = core.HomeAssistant()
|
hass = core.HomeAssistant()
|
||||||
|
|
||||||
# Set config dir to directory holding config file
|
# Set config dir to directory holding config file
|
||||||
hass.config.config_dir = os.path.abspath(os.path.dirname(config_path))
|
config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||||
|
hass.config.config_dir = config_dir
|
||||||
|
mount_local_lib_path(config_dir)
|
||||||
|
|
||||||
|
enable_logging(hass)
|
||||||
|
|
||||||
config_dict = config_util.load_config_file(config_path)
|
config_dict = config_util.load_config_file(config_path)
|
||||||
|
|
||||||
return from_config_dict(config_dict, hass)
|
return from_config_dict(config_dict, hass, enable_log=False)
|
||||||
|
|
||||||
|
|
||||||
def enable_logging(hass):
|
def enable_logging(hass):
|
||||||
|
@ -62,8 +62,12 @@ def _get_action(hass, config):
|
|||||||
service_data = {}
|
service_data = {}
|
||||||
|
|
||||||
if CONF_SERVICE_ENTITY_ID in config:
|
if CONF_SERVICE_ENTITY_ID in config:
|
||||||
|
try:
|
||||||
service_data[ATTR_ENTITY_ID] = \
|
service_data[ATTR_ENTITY_ID] = \
|
||||||
config[CONF_SERVICE_ENTITY_ID].split(",")
|
config[CONF_SERVICE_ENTITY_ID].split(",")
|
||||||
|
except AttributeError:
|
||||||
|
service_data[ATTR_ENTITY_ID] = \
|
||||||
|
config[CONF_SERVICE_ENTITY_ID]
|
||||||
|
|
||||||
hass.services.call(domain, service, service_data)
|
hass.services.call(domain, service, service_data)
|
||||||
|
|
||||||
|
@ -10,11 +10,11 @@ import homeassistant.core as ha
|
|||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_PLATFORM, ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID)
|
CONF_PLATFORM, ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
|
||||||
|
|
||||||
DOMAIN = "demo"
|
DOMAIN = "demo"
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = ['introduction', 'conversation']
|
||||||
|
|
||||||
COMPONENTS_WITH_DEMO_PLATFORM = [
|
COMPONENTS_WITH_DEMO_PLATFORM = [
|
||||||
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
|
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
|
||||||
@ -48,8 +48,11 @@ def setup(hass, config):
|
|||||||
# Setup room groups
|
# Setup room groups
|
||||||
lights = hass.states.entity_ids('light')
|
lights = hass.states.entity_ids('light')
|
||||||
switches = hass.states.entity_ids('switch')
|
switches = hass.states.entity_ids('switch')
|
||||||
group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0]])
|
media_players = sorted(hass.states.entity_ids('media_player'))
|
||||||
group.setup_group(hass, 'bedroom', [lights[2], switches[1]])
|
group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0],
|
||||||
|
media_players[1]])
|
||||||
|
group.setup_group(hass, 'bedroom', [lights[2], switches[1],
|
||||||
|
media_players[0]])
|
||||||
|
|
||||||
# Setup IP Camera
|
# Setup IP Camera
|
||||||
bootstrap.setup_component(
|
bootstrap.setup_component(
|
||||||
@ -102,10 +105,10 @@ def setup(hass, config):
|
|||||||
# Setup fake device tracker
|
# Setup fake device tracker
|
||||||
hass.states.set("device_tracker.paulus", "home",
|
hass.states.set("device_tracker.paulus", "home",
|
||||||
{ATTR_ENTITY_PICTURE:
|
{ATTR_ENTITY_PICTURE:
|
||||||
"http://graph.facebook.com/297400035/picture"})
|
"http://graph.facebook.com/297400035/picture",
|
||||||
|
ATTR_FRIENDLY_NAME: 'Paulus'})
|
||||||
hass.states.set("device_tracker.anne_therese", "not_home",
|
hass.states.set("device_tracker.anne_therese", "not_home",
|
||||||
{ATTR_ENTITY_PICTURE:
|
{ATTR_FRIENDLY_NAME: 'Anne Therese'})
|
||||||
"http://graph.facebook.com/621994601/picture"})
|
|
||||||
|
|
||||||
hass.states.set("group.all_devices", "home",
|
hass.states.set("group.all_devices", "home",
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,7 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.helpers.entity import _OVERWRITE
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@ -66,14 +67,15 @@ def setup(hass, config):
|
|||||||
'device_tracker.{}'.format(tracker_type))
|
'device_tracker.{}'.format(tracker_type))
|
||||||
|
|
||||||
if tracker_implementation is None:
|
if tracker_implementation is None:
|
||||||
_LOGGER.error("Unknown device_tracker type specified.")
|
_LOGGER.error("Unknown device_tracker type specified: %s.",
|
||||||
|
tracker_type)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
device_scanner = tracker_implementation.get_scanner(hass, config)
|
device_scanner = tracker_implementation.get_scanner(hass, config)
|
||||||
|
|
||||||
if device_scanner is None:
|
if device_scanner is None:
|
||||||
_LOGGER.error("Failed to initialize device scanner for %s",
|
_LOGGER.error("Failed to initialize device scanner: %s",
|
||||||
tracker_type)
|
tracker_type)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -161,9 +163,12 @@ class DeviceTracker(object):
|
|||||||
|
|
||||||
state = STATE_HOME if is_home else STATE_NOT_HOME
|
state = STATE_HOME if is_home else STATE_NOT_HOME
|
||||||
|
|
||||||
|
# overwrite properties that have been set in the config file
|
||||||
|
attr = dict(dev_info['state_attr'])
|
||||||
|
attr.update(_OVERWRITE.get(dev_info['entity_id'], {}))
|
||||||
|
|
||||||
self.hass.states.set(
|
self.hass.states.set(
|
||||||
dev_info['entity_id'], state,
|
dev_info['entity_id'], state, attr)
|
||||||
dev_info['state_attr'])
|
|
||||||
|
|
||||||
def update_devices(self, now):
|
def update_devices(self, now):
|
||||||
""" Update device states based on the found devices. """
|
""" Update device states based on the found devices. """
|
||||||
|
149
homeassistant/components/device_tracker/actiontec.py
Normal file
149
homeassistant/components/device_tracker/actiontec.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.device_tracker.actiontec
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Device tracker platform that supports scanning an Actiontec MI424WR
|
||||||
|
(Verizon FIOS) router for device presence.
|
||||||
|
|
||||||
|
This device tracker needs telnet to be enabled on the router.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Actiontec tracker you will need to add something like the
|
||||||
|
following to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: actiontec
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_LEASES_REGEX = re.compile(
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})' +
|
||||||
|
r'\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))')
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
""" Validates config and returns a DD-WRT scanner. """
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
scanner = ActiontecDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
|
|
||||||
|
class ActiontecDeviceScanner(object):
|
||||||
|
""" This class queries a an actiontec router
|
||||||
|
for connected devices. Adapted from DD-WRT scanner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.host = config[CONF_HOST]
|
||||||
|
self.username = config[CONF_USERNAME]
|
||||||
|
self.password = config[CONF_PASSWORD]
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
self.last_results = {}
|
||||||
|
|
||||||
|
# Test the router is accessible
|
||||||
|
data = self.get_actiontec_data()
|
||||||
|
self.success_init = data is not None
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
return [client['mac'] for client in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" Returns the name of the given device or None if we don't know. """
|
||||||
|
if not self.last_results:
|
||||||
|
return None
|
||||||
|
for client in self.last_results:
|
||||||
|
if client['mac'] == device:
|
||||||
|
return client['ip']
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def _update_info(self):
|
||||||
|
""" Ensures the information from the Actiontec MI424WR router is up
|
||||||
|
to date. Returns boolean if scanning successful. """
|
||||||
|
if not self.success_init:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
# _LOGGER.info("Checking ARP")
|
||||||
|
data = self.get_actiontec_data()
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
active_clients = [client for client in data.values()]
|
||||||
|
self.last_results = active_clients
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_actiontec_data(self):
|
||||||
|
""" Retrieve data from Actiontec MI424WR and return parsed result. """
|
||||||
|
try:
|
||||||
|
telnet = telnetlib.Telnet(self.host)
|
||||||
|
telnet.read_until(b'Username: ')
|
||||||
|
telnet.write((self.username + '\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'Password: ')
|
||||||
|
telnet.write((self.password + '\n').encode('ascii'))
|
||||||
|
prompt = telnet.read_until(
|
||||||
|
b'Wireless Broadband Router> ').split(b'\n')[-1]
|
||||||
|
telnet.write('firewall mac_cache_dump\n'.encode('ascii'))
|
||||||
|
telnet.write('\n'.encode('ascii'))
|
||||||
|
telnet.read_until(prompt)
|
||||||
|
leases_result = telnet.read_until(prompt).split(b'\n')[1:-1]
|
||||||
|
telnet.write('exit\n'.encode('ascii'))
|
||||||
|
except EOFError:
|
||||||
|
_LOGGER.exception("Unexpected response from router")
|
||||||
|
return
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
_LOGGER.exception("Connection refused by router," +
|
||||||
|
" is telnet enabled?")
|
||||||
|
return None
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for lease in leases_result:
|
||||||
|
match = _LEASES_REGEX.search(lease.decode('utf-8'))
|
||||||
|
if match is not None:
|
||||||
|
devices[match.group('ip')] = {
|
||||||
|
'ip': match.group('ip'),
|
||||||
|
'mac': match.group('mac').upper()
|
||||||
|
}
|
||||||
|
return devices
|
@ -35,7 +35,6 @@ from datetime import timedelta
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
from homeassistant.helpers import validate_config
|
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
from homeassistant.components.device_tracker import DOMAIN
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
@ -43,20 +42,21 @@ from homeassistant.components.device_tracker import DOMAIN
|
|||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['pynetgear>=0.1']
|
REQUIREMENTS = ['pynetgear==0.3']
|
||||||
|
|
||||||
|
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
""" Validates config and returns a Netgear scanner. """
|
""" Validates config and returns a Netgear scanner. """
|
||||||
if not validate_config(config,
|
info = config[DOMAIN]
|
||||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
host = info.get(CONF_HOST)
|
||||||
_LOGGER):
|
username = info.get(CONF_USERNAME)
|
||||||
|
password = info.get(CONF_PASSWORD)
|
||||||
|
|
||||||
|
if password is not None and host is None:
|
||||||
|
_LOGGER.warning('Found username or password but no host')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
info = config[DOMAIN]
|
scanner = NetgearDeviceScanner(host, username, password)
|
||||||
|
|
||||||
scanner = NetgearDeviceScanner(
|
|
||||||
info[CONF_HOST], info[CONF_USERNAME], info[CONF_PASSWORD])
|
|
||||||
|
|
||||||
return scanner if scanner.success_init else None
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
@ -68,16 +68,24 @@ class NetgearDeviceScanner(object):
|
|||||||
import pynetgear
|
import pynetgear
|
||||||
|
|
||||||
self.last_results = []
|
self.last_results = []
|
||||||
|
|
||||||
self._api = pynetgear.Netgear(host, username, password)
|
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
if host is None:
|
||||||
|
print("BIER")
|
||||||
|
self._api = pynetgear.Netgear()
|
||||||
|
elif username is None:
|
||||||
|
self._api = pynetgear.Netgear(password, host)
|
||||||
|
else:
|
||||||
|
self._api = pynetgear.Netgear(password, host, username)
|
||||||
|
|
||||||
_LOGGER.info("Logging in")
|
_LOGGER.info("Logging in")
|
||||||
|
|
||||||
self.success_init = self._api.login()
|
results = self._api.get_attached_devices()
|
||||||
|
|
||||||
|
self.success_init = results is not None
|
||||||
|
|
||||||
if self.success_init:
|
if self.success_init:
|
||||||
self._update_info()
|
self.last_results = results
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Failed to Login")
|
_LOGGER.error("Failed to Login")
|
||||||
|
|
||||||
|
@ -26,8 +26,12 @@ from collections import namedtuple
|
|||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from libnmap.process import NmapProcess
|
try:
|
||||||
from libnmap.parser import NmapParser, NmapParserException
|
from libnmap.process import NmapProcess
|
||||||
|
from libnmap.parser import NmapParser, NmapParserException
|
||||||
|
LIB_LOADED = True
|
||||||
|
except ImportError:
|
||||||
|
LIB_LOADED = False
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.const import CONF_HOSTS
|
from homeassistant.const import CONF_HOSTS
|
||||||
@ -43,7 +47,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# interval in minutes to exclude devices from a scan while they are home
|
# interval in minutes to exclude devices from a scan while they are home
|
||||||
CONF_HOME_INTERVAL = "home_interval"
|
CONF_HOME_INTERVAL = "home_interval"
|
||||||
|
|
||||||
REQUIREMENTS = ['python-libnmap>=0.6.2']
|
REQUIREMENTS = ['python-libnmap==0.6.1']
|
||||||
|
|
||||||
|
|
||||||
def get_scanner(hass, config):
|
def get_scanner(hass, config):
|
||||||
@ -52,6 +56,10 @@ def get_scanner(hass, config):
|
|||||||
_LOGGER):
|
_LOGGER):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if not LIB_LOADED:
|
||||||
|
_LOGGER.error("Error while importing dependency python-libnmap.")
|
||||||
|
return False
|
||||||
|
|
||||||
scanner = NmapDeviceScanner(config[DOMAIN])
|
scanner = NmapDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
return scanner if scanner.success_init else None
|
return scanner if scanner.success_init else None
|
||||||
|
157
homeassistant/components/device_tracker/thomson.py
Normal file
157
homeassistant/components/device_tracker/thomson.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.device_tracker.thomson
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Device tracker platform that supports scanning a THOMSON router for device
|
||||||
|
presence.
|
||||||
|
|
||||||
|
This device tracker needs telnet to be enabled on the router.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the THOMSON tracker you will need to add something like the following
|
||||||
|
to your config/configuration.yaml
|
||||||
|
|
||||||
|
device_tracker:
|
||||||
|
platform: thomson
|
||||||
|
host: YOUR_ROUTER_IP
|
||||||
|
username: YOUR_ADMIN_USERNAME
|
||||||
|
password: YOUR_ADMIN_PASSWORD
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
host
|
||||||
|
*Required
|
||||||
|
The IP address of your router, e.g. 192.168.1.1.
|
||||||
|
|
||||||
|
username
|
||||||
|
*Required
|
||||||
|
The username of an user with administrative privileges, usually 'admin'.
|
||||||
|
|
||||||
|
password
|
||||||
|
*Required
|
||||||
|
The password for your given admin account.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
|
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_DEVICES_REGEX = re.compile(
|
||||||
|
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' +
|
||||||
|
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
|
||||||
|
r'(?P<status>([^\s]+))\s+' +
|
||||||
|
r'(?P<type>([^\s]+))\s+' +
|
||||||
|
r'(?P<intf>([^\s]+))\s+' +
|
||||||
|
r'(?P<hwintf>([^\s]+))\s+' +
|
||||||
|
r'(?P<host>([^\s]+))')
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def get_scanner(hass, config):
|
||||||
|
""" Validates config and returns a THOMSON scanner. """
|
||||||
|
if not validate_config(config,
|
||||||
|
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||||
|
_LOGGER):
|
||||||
|
return None
|
||||||
|
|
||||||
|
scanner = ThomsonDeviceScanner(config[DOMAIN])
|
||||||
|
|
||||||
|
return scanner if scanner.success_init else None
|
||||||
|
|
||||||
|
|
||||||
|
class ThomsonDeviceScanner(object):
|
||||||
|
""" This class queries a router running THOMSON firmware
|
||||||
|
for connected devices. Adapted from ASUSWRT scanner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.host = config[CONF_HOST]
|
||||||
|
self.username = config[CONF_USERNAME]
|
||||||
|
self.password = config[CONF_PASSWORD]
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
self.last_results = {}
|
||||||
|
|
||||||
|
# Test the router is accessible
|
||||||
|
data = self.get_thomson_data()
|
||||||
|
self.success_init = data is not None
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
return [client['mac'] for client in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" Returns the name of the given device
|
||||||
|
or None if we don't know. """
|
||||||
|
if not self.last_results:
|
||||||
|
return None
|
||||||
|
for client in self.last_results:
|
||||||
|
if client['mac'] == device:
|
||||||
|
return client['host']
|
||||||
|
return None
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
|
def _update_info(self):
|
||||||
|
""" Ensures the information from the THOMSON router is up to date.
|
||||||
|
Returns boolean if scanning successful. """
|
||||||
|
if not self.success_init:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
_LOGGER.info("Checking ARP")
|
||||||
|
data = self.get_thomson_data()
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# flag C stands for CONNECTED
|
||||||
|
active_clients = [client for client in data.values() if
|
||||||
|
client['status'].find('C') != -1]
|
||||||
|
self.last_results = active_clients
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_thomson_data(self):
|
||||||
|
""" Retrieve data from THOMSON and return parsed result. """
|
||||||
|
try:
|
||||||
|
telnet = telnetlib.Telnet(self.host)
|
||||||
|
telnet.read_until(b'Username : ')
|
||||||
|
telnet.write((self.username + '\r\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'Password : ')
|
||||||
|
telnet.write((self.password + '\r\n').encode('ascii'))
|
||||||
|
telnet.read_until(b'=>')
|
||||||
|
telnet.write(('hostmgr list\r\n').encode('ascii'))
|
||||||
|
devices_result = telnet.read_until(b'=>').split(b'\r\n')
|
||||||
|
telnet.write('exit\r\n'.encode('ascii'))
|
||||||
|
except EOFError:
|
||||||
|
_LOGGER.exception("Unexpected response from router")
|
||||||
|
return
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
_LOGGER.exception("Connection refused by router," +
|
||||||
|
" is telnet enabled?")
|
||||||
|
return
|
||||||
|
|
||||||
|
devices = {}
|
||||||
|
for device in devices_result:
|
||||||
|
match = _DEVICES_REGEX.search(device.decode('utf-8'))
|
||||||
|
if match:
|
||||||
|
devices[match.group('ip')] = {
|
||||||
|
'ip': match.group('ip'),
|
||||||
|
'mac': match.group('mac').upper(),
|
||||||
|
'host': match.group('host'),
|
||||||
|
'status': match.group('status')
|
||||||
|
}
|
||||||
|
return devices
|
@ -19,7 +19,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "discovery"
|
DOMAIN = "discovery"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['netdisco>=0.1']
|
REQUIREMENTS = ['netdisco==0.3']
|
||||||
|
|
||||||
SCAN_INTERVAL = 300 # seconds
|
SCAN_INTERVAL = 300 # seconds
|
||||||
|
|
||||||
@ -28,11 +28,13 @@ SCAN_INTERVAL = 300 # seconds
|
|||||||
SERVICE_WEMO = 'belkin_wemo'
|
SERVICE_WEMO = 'belkin_wemo'
|
||||||
SERVICE_HUE = 'philips_hue'
|
SERVICE_HUE = 'philips_hue'
|
||||||
SERVICE_CAST = 'google_cast'
|
SERVICE_CAST = 'google_cast'
|
||||||
|
SERVICE_NETGEAR = 'netgear_router'
|
||||||
|
|
||||||
SERVICE_HANDLERS = {
|
SERVICE_HANDLERS = {
|
||||||
SERVICE_WEMO: "switch",
|
SERVICE_WEMO: "switch",
|
||||||
SERVICE_CAST: "media_player",
|
SERVICE_CAST: "media_player",
|
||||||
SERVICE_HUE: "light",
|
SERVICE_HUE: "light",
|
||||||
|
SERVICE_NETGEAR: 'device_tracker',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -77,6 +79,13 @@ def setup(hass, config):
|
|||||||
if not component:
|
if not component:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Hack - fix when device_tracker supports discovery
|
||||||
|
if service == SERVICE_NETGEAR:
|
||||||
|
bootstrap.setup_component(hass, component, {
|
||||||
|
'device_tracker': {'platform': 'netgear'}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
# This component cannot be setup.
|
# This component cannot be setup.
|
||||||
if not bootstrap.setup_component(hass, component, config):
|
if not bootstrap.setup_component(hass, component, config):
|
||||||
return
|
return
|
||||||
|
@ -5,22 +5,44 @@
|
|||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
|
|
||||||
<link rel='manifest' href='/static/manifest.json' />
|
<link rel='manifest' href='/static/manifest.json' />
|
||||||
|
|
||||||
<meta name='apple-mobile-web-app-capable' content='yes'>
|
|
||||||
<meta name='mobile-web-app-capable' content='yes'>
|
|
||||||
|
|
||||||
<meta name='viewport' content='width=device-width,
|
|
||||||
user-scalable=no' />
|
|
||||||
|
|
||||||
<link rel='shortcut icon' href='/static/favicon.ico' />
|
<link rel='shortcut icon' href='/static/favicon.ico' />
|
||||||
<link rel='icon' type='image/png'
|
<link rel='icon' type='image/png'
|
||||||
href='/static/favicon-192x192.png' sizes='192x192'>
|
href='/static/favicon-192x192.png' sizes='192x192'>
|
||||||
<link rel='apple-touch-icon' sizes='180x180'
|
<link rel='apple-touch-icon' sizes='180x180'
|
||||||
href='/static/favicon-apple-180x180.png'>
|
href='/static/favicon-apple-180x180.png'>
|
||||||
|
<meta name='apple-mobile-web-app-capable' content='yes'>
|
||||||
|
<meta name='mobile-web-app-capable' content='yes'>
|
||||||
|
<meta name='viewport' content='width=device-width,
|
||||||
|
user-scalable=no' />
|
||||||
<meta name='theme-color' content='#03a9f4'>
|
<meta name='theme-color' content='#03a9f4'>
|
||||||
|
<style>
|
||||||
|
#init {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Roboto', 'Noto', sans-serif;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
#init p {
|
||||||
|
margin-bottom: 101px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body fullbleed>
|
<body fullbleed>
|
||||||
<h3 id='init' align='center'>Initializing Home Assistant</h3>
|
<div id='init'>
|
||||||
|
<img src='/static/splash.png' height='230' />
|
||||||
|
<p>Initializing</p>
|
||||||
|
</div>
|
||||||
<script src='/static/webcomponents-lite.min.js'></script>
|
<script src='/static/webcomponents-lite.min.js'></script>
|
||||||
<link rel='import' href='/static/{{ app_url }}' />
|
<link rel='import' href='/static/{{ app_url }}' />
|
||||||
<home-assistant auth='{{ auth }}'></home-assistant>
|
<home-assistant auth='{{ auth }}'></home-assistant>
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "541d58d78257af6cf484b923f3b39c1e"
|
VERSION = "e9060d58fc9034468cfefa9794026d0c"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
Subproject commit 576c04efb49a8a5f7f35734458ffc93f874dd68d
|
Subproject commit a97750b5dd887af42030e01bfe50bc3c60183514
|
BIN
homeassistant/components/frontend/www_static/splash.png
Normal file
BIN
homeassistant/components/frontend/www_static/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
@ -1,2 +0,0 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
|
||||||
VERSION = ""
|
|
41
homeassistant/components/introduction.py
Normal file
41
homeassistant/components/introduction.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.introduction
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Component that will help guide the user taking its first steps.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
DOMAIN = 'introduction'
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config=None):
|
||||||
|
""" Setup the introduction component. """
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.info("""
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Hello, and welcome to Home Assistant!
|
||||||
|
|
||||||
|
We'll hope that we can make all your dreams come true.
|
||||||
|
|
||||||
|
Here are some resources to get started:
|
||||||
|
|
||||||
|
- Configuring Home Assistant:
|
||||||
|
https://home-assistant.io/getting-started/configuration.html
|
||||||
|
|
||||||
|
- Available components:
|
||||||
|
https://home-assistant.io/components/
|
||||||
|
|
||||||
|
- Chat room:
|
||||||
|
https://gitter.im/balloob/home-assistant
|
||||||
|
|
||||||
|
This message is generated by the introduction component. You can
|
||||||
|
disable it in configuration.yaml.
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
""")
|
||||||
|
|
||||||
|
return True
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "isy994"
|
DOMAIN = "isy994"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['PyISY>=1.0.5']
|
REQUIREMENTS = ['PyISY==1.0.5']
|
||||||
DISCOVER_LIGHTS = "isy994.lights"
|
DISCOVER_LIGHTS = "isy994.lights"
|
||||||
DISCOVER_SWITCHES = "isy994.switches"
|
DISCOVER_SWITCHES = "isy994.switches"
|
||||||
DISCOVER_SENSORS = "isy994.sensors"
|
DISCOVER_SENSORS = "isy994.sensors"
|
||||||
@ -156,6 +156,12 @@ class ISYDeviceABC(ToggleEntity):
|
|||||||
attr = {ATTR_FRIENDLY_NAME: self.name}
|
attr = {ATTR_FRIENDLY_NAME: self.name}
|
||||||
for name, prop in self._attrs.items():
|
for name, prop in self._attrs.items():
|
||||||
attr[name] = getattr(self, prop)
|
attr[name] = getattr(self, prop)
|
||||||
|
attr = self._attr_filter(attr)
|
||||||
|
return attr
|
||||||
|
|
||||||
|
def _attr_filter(self, attr):
|
||||||
|
""" Placeholder for attribute filters. """
|
||||||
|
# pylint: disable=no-self-use
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "keyboard"
|
DOMAIN = "keyboard"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['pyuserinput>=0.1.9']
|
REQUIREMENTS = ['pyuserinput==0.1.9']
|
||||||
|
|
||||||
|
|
||||||
def volume_up(hass):
|
def volume_up(hass):
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.components.light import (
|
|||||||
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT,
|
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT,
|
||||||
EFFECT_COLORLOOP)
|
EFFECT_COLORLOOP)
|
||||||
|
|
||||||
REQUIREMENTS = ['phue>=0.8']
|
REQUIREMENTS = ['phue==0.8']
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if discovery_info is not None:
|
if discovery_info is not None:
|
||||||
host = urlparse(discovery_info).hostname
|
host = urlparse(discovery_info[1]).hostname
|
||||||
else:
|
else:
|
||||||
host = config.get(CONF_HOST, None)
|
host = config.get(CONF_HOST, None)
|
||||||
|
|
||||||
|
@ -38,3 +38,9 @@ class ISYLightDevice(ISYDeviceABC):
|
|||||||
_attrs = {ATTR_BRIGHTNESS: 'value'}
|
_attrs = {ATTR_BRIGHTNESS: 'value'}
|
||||||
_onattrs = [ATTR_BRIGHTNESS]
|
_onattrs = [ATTR_BRIGHTNESS]
|
||||||
_states = [STATE_ON, STATE_OFF]
|
_states = [STATE_ON, STATE_OFF]
|
||||||
|
|
||||||
|
def _attr_filter(self, attr):
|
||||||
|
""" Filter brightness out of entity while off. """
|
||||||
|
if ATTR_BRIGHTNESS in attr and not self.is_on:
|
||||||
|
del attr[ATTR_BRIGHTNESS]
|
||||||
|
return attr
|
||||||
|
@ -34,7 +34,7 @@ from homeassistant.components.light import (Light, ATTR_BRIGHTNESS,
|
|||||||
from homeassistant.util.color import color_RGB_to_xy
|
from homeassistant.util.color import color_RGB_to_xy
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['ledcontroller>=1.0.7']
|
REQUIREMENTS = ['ledcontroller==1.0.7']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
@ -9,7 +9,7 @@ from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
|||||||
from homeassistant.const import ATTR_FRIENDLY_NAME
|
from homeassistant.const import ATTR_FRIENDLY_NAME
|
||||||
import tellcore.constants as tellcore_constants
|
import tellcore.constants as tellcore_constants
|
||||||
|
|
||||||
REQUIREMENTS = ['tellcore-py>=1.0.4']
|
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
@ -9,8 +9,8 @@ from homeassistant.components.light import ATTR_BRIGHTNESS
|
|||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
|
||||||
'#pywink>=0.1']
|
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
@ -8,14 +8,9 @@ WARNING: This platform is currently not working due to a changed Cast API
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
|
||||||
import pychromecast
|
|
||||||
except ImportError:
|
|
||||||
pychromecast = None
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
|
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
|
||||||
STATE_UNKNOWN)
|
STATE_UNKNOWN, CONF_HOST)
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
MediaPlayerDevice,
|
MediaPlayerDevice,
|
||||||
@ -24,7 +19,7 @@ from homeassistant.components.media_player import (
|
|||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
||||||
|
|
||||||
REQUIREMENTS = ['pychromecast>=0.6.9']
|
REQUIREMENTS = ['pychromecast==0.6.10']
|
||||||
CONF_IGNORE_CEC = 'ignore_cec'
|
CONF_IGNORE_CEC = 'ignore_cec'
|
||||||
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
||||||
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
@ -32,21 +27,23 @@ SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
|||||||
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE
|
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE
|
||||||
KNOWN_HOSTS = []
|
KNOWN_HOSTS = []
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
cast = None
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the cast platform. """
|
""" Sets up the cast platform. """
|
||||||
global pychromecast # pylint: disable=invalid-name
|
global cast
|
||||||
if pychromecast is None:
|
import pychromecast
|
||||||
import pychromecast as pychromecast_
|
cast = pychromecast
|
||||||
pychromecast = pychromecast_
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# import CEC IGNORE attributes
|
# import CEC IGNORE attributes
|
||||||
ignore_cec = config.get(CONF_IGNORE_CEC, [])
|
ignore_cec = config.get(CONF_IGNORE_CEC, [])
|
||||||
if isinstance(ignore_cec, list):
|
if isinstance(ignore_cec, list):
|
||||||
pychromecast.IGNORE_CEC += ignore_cec
|
cast.IGNORE_CEC += ignore_cec
|
||||||
else:
|
else:
|
||||||
logger.error('Chromecast conig, %s must be a list.', CONF_IGNORE_CEC)
|
logger.error('Chromecast conig, %s must be a list.', CONF_IGNORE_CEC)
|
||||||
|
|
||||||
@ -55,9 +52,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if discovery_info and discovery_info[0] not in KNOWN_HOSTS:
|
if discovery_info and discovery_info[0] not in KNOWN_HOSTS:
|
||||||
hosts = [discovery_info[0]]
|
hosts = [discovery_info[0]]
|
||||||
|
|
||||||
|
elif CONF_HOST in config:
|
||||||
|
hosts = [config[CONF_HOST]]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
hosts = (host_port[0] for host_port
|
hosts = (host_port[0] for host_port
|
||||||
in pychromecast.discover_chromecasts()
|
in cast.discover_chromecasts()
|
||||||
if host_port[0] not in KNOWN_HOSTS)
|
if host_port[0] not in KNOWN_HOSTS)
|
||||||
|
|
||||||
casts = []
|
casts = []
|
||||||
@ -65,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
for host in hosts:
|
for host in hosts:
|
||||||
try:
|
try:
|
||||||
casts.append(CastDevice(host))
|
casts.append(CastDevice(host))
|
||||||
except pychromecast.ChromecastConnectionError:
|
except cast.ChromecastConnectionError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
KNOWN_HOSTS.append(host)
|
KNOWN_HOSTS.append(host)
|
||||||
@ -80,7 +80,7 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
import pychromecast.controllers.youtube as youtube
|
import pychromecast.controllers.youtube as youtube
|
||||||
self.cast = pychromecast.Chromecast(host)
|
self.cast = cast.Chromecast(host)
|
||||||
self.youtube = youtube.YouTubeController()
|
self.youtube = youtube.YouTubeController()
|
||||||
self.cast.register_handler(self.youtube)
|
self.cast.register_handler(self.youtube)
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
self.cast.quit_app()
|
self.cast.quit_app()
|
||||||
|
|
||||||
self.cast.play_media(
|
self.cast.play_media(
|
||||||
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
|
CAST_SPLASH, cast.STREAM_TYPE_BUFFERED)
|
||||||
|
|
||||||
def turn_off(self):
|
def turn_off(self):
|
||||||
""" Turns Chromecast off. """
|
""" Turns Chromecast off. """
|
||||||
|
@ -48,7 +48,7 @@ except ImportError:
|
|||||||
jsonrpc_requests = None
|
jsonrpc_requests = None
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['jsonrpc-requests>=0.1']
|
REQUIREMENTS = ['jsonrpc-requests==0.1']
|
||||||
|
|
||||||
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
||||||
|
@ -48,7 +48,7 @@ from homeassistant.components.media_player import (
|
|||||||
MEDIA_TYPE_MUSIC)
|
MEDIA_TYPE_MUSIC)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['python-mpd2>=0.5.4']
|
REQUIREMENTS = ['python-mpd2==0.5.4']
|
||||||
|
|
||||||
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||||
|
@ -38,8 +38,8 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
|||||||
DOMAIN = "modbus"
|
DOMAIN = "modbus"
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/python3.zip'
|
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' +
|
||||||
'#pymodbus>=1.2.0']
|
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip']
|
||||||
|
|
||||||
# Type of network
|
# Type of network
|
||||||
MEDIUM = "type"
|
MEDIUM = "type"
|
||||||
|
@ -46,7 +46,7 @@ The keep alive in seconds for this client. Default is 60.
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -66,7 +66,7 @@ SERVICE_PUBLISH = 'publish'
|
|||||||
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
|
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['paho-mqtt>=1.1']
|
REQUIREMENTS = ['paho-mqtt==1.1']
|
||||||
|
|
||||||
CONF_BROKER = 'broker'
|
CONF_BROKER = 'broker'
|
||||||
CONF_PORT = 'port'
|
CONF_PORT = 'port'
|
||||||
|
@ -4,12 +4,13 @@ homeassistant.components.notify
|
|||||||
|
|
||||||
Provides functionality to notify people.
|
Provides functionality to notify people.
|
||||||
"""
|
"""
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import config_per_platform
|
||||||
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_NAME
|
||||||
|
|
||||||
DOMAIN = "notify"
|
DOMAIN = "notify"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -33,29 +34,28 @@ def send_message(hass, message):
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Sets up notify services. """
|
""" Sets up notify services. """
|
||||||
|
success = False
|
||||||
|
|
||||||
if not validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER):
|
for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER):
|
||||||
return False
|
# get platform
|
||||||
|
|
||||||
platform = config[DOMAIN].get(CONF_PLATFORM)
|
|
||||||
|
|
||||||
notify_implementation = get_component(
|
notify_implementation = get_component(
|
||||||
'notify.{}'.format(platform))
|
'notify.{}'.format(platform))
|
||||||
|
|
||||||
if notify_implementation is None:
|
if notify_implementation is None:
|
||||||
_LOGGER.error("Unknown notification service specified.")
|
_LOGGER.error("Unknown notification service specified.")
|
||||||
|
continue
|
||||||
|
|
||||||
return False
|
# create platform service
|
||||||
|
notify_service = notify_implementation.get_service(
|
||||||
notify_service = notify_implementation.get_service(hass, config)
|
hass, {DOMAIN: p_config})
|
||||||
|
|
||||||
if notify_service is None:
|
if notify_service is None:
|
||||||
_LOGGER.error("Failed to initialize notification service %s",
|
_LOGGER.error("Failed to initialize notification service %s",
|
||||||
platform)
|
platform)
|
||||||
|
continue
|
||||||
|
|
||||||
return False
|
# create service handler
|
||||||
|
def notify_message(notify_service, call):
|
||||||
def notify_message(call):
|
|
||||||
""" Handle sending notification message service calls. """
|
""" Handle sending notification message service calls. """
|
||||||
message = call.data.get(ATTR_MESSAGE)
|
message = call.data.get(ATTR_MESSAGE)
|
||||||
|
|
||||||
@ -66,9 +66,13 @@ def setup(hass, config):
|
|||||||
|
|
||||||
notify_service.send_message(message, title=title)
|
notify_service.send_message(message, title=title)
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_NOTIFY, notify_message)
|
# register service
|
||||||
|
service_call_handler = partial(notify_message, notify_service)
|
||||||
|
service_notify = p_config.get(CONF_NAME, SERVICE_NOTIFY)
|
||||||
|
hass.services.register(DOMAIN, service_notify, service_call_handler)
|
||||||
|
success = True
|
||||||
|
|
||||||
return True
|
return success
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
@ -28,7 +28,7 @@ from homeassistant.components.notify import (
|
|||||||
from homeassistant.const import CONF_API_KEY
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['pushbullet.py>=0.7.1']
|
REQUIREMENTS = ['pushbullet.py==0.7.1']
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
|
@ -42,7 +42,7 @@ from homeassistant.components.notify import (
|
|||||||
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||||
from homeassistant.const import CONF_API_KEY
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
REQUIREMENTS = ['python-pushover>=0.2']
|
REQUIREMENTS = ['python-pushover==0.2']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ from homeassistant.components.notify import (
|
|||||||
DOMAIN, BaseNotificationService)
|
DOMAIN, BaseNotificationService)
|
||||||
from homeassistant.const import CONF_API_KEY
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
REQUIREMENTS = ['slacker>=0.6.8']
|
REQUIREMENTS = ['slacker==0.6.8']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.notify.xmpp
|
homeassistant.components.notify.xmpp
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Jabber (XMPP) notification service.
|
Jabber (XMPP) notification service.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
@ -29,7 +28,6 @@ The password for your given Jabber account.
|
|||||||
recipient
|
recipient
|
||||||
*Required
|
*Required
|
||||||
The Jabber ID (JID) that will receive the messages.
|
The Jabber ID (JID) that will receive the messages.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -47,7 +45,7 @@ from homeassistant.helpers import validate_config
|
|||||||
from homeassistant.components.notify import (
|
from homeassistant.components.notify import (
|
||||||
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||||
|
|
||||||
REQUIREMENTS = ['sleekxmpp>=1.3.1']
|
REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0']
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
|
@ -131,7 +131,7 @@ class ServiceEventListener(EventListener):
|
|||||||
def execute(self, hass):
|
def execute(self, hass):
|
||||||
""" Call the service. """
|
""" Call the service. """
|
||||||
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids}
|
||||||
hass.call_service(self.domain, self.service, data)
|
hass.services.call(self.domain, self.service, data)
|
||||||
|
|
||||||
# Reschedule for next day
|
# Reschedule for next day
|
||||||
self.schedule(hass)
|
self.schedule(hass)
|
||||||
|
@ -71,7 +71,7 @@ from homeassistant.util import Throttle
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ['blockchain>=1.1.2']
|
REQUIREMENTS = ['blockchain==1.1.2']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
OPTION_TYPES = {
|
OPTION_TYPES = {
|
||||||
'wallet': ['Wallet balance', 'BTC'],
|
'wallet': ['Wallet balance', 'BTC'],
|
||||||
|
@ -15,6 +15,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices([
|
add_devices([
|
||||||
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12),
|
DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS, 12),
|
||||||
DemoSensor('Outside Humidity', 54, '%', None),
|
DemoSensor('Outside Humidity', 54, '%', None),
|
||||||
|
DemoSensor('Alarm back', 'Armed', None, None),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
164
homeassistant/components/sensor/dht.py
Normal file
164
homeassistant/components/sensor/dht.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.sensor.dht
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Adafruit DHT temperature and humidity sensor.
|
||||||
|
You need a Python3 compatible version of the Adafruit_Python_DHT library
|
||||||
|
(e.g. https://github.com/mala-zaba/Adafruit_Python_DHT,
|
||||||
|
also see requirements.txt).
|
||||||
|
As this requires access to the GPIO, you will need to run home-assistant
|
||||||
|
as root.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
To use the Adafruit DHT sensor you will need to
|
||||||
|
add something like the following to your config/configuration.yaml:
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: dht
|
||||||
|
sensor: DHT22
|
||||||
|
pin: 23
|
||||||
|
monitored_conditions:
|
||||||
|
- temperature
|
||||||
|
- humidity
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
sensor
|
||||||
|
*Required
|
||||||
|
The sensor type, DHT11, DHT22 or AM2302
|
||||||
|
|
||||||
|
pin
|
||||||
|
*Required
|
||||||
|
The pin the sensor is connected to, something like
|
||||||
|
'P8_11' for Beaglebone, '23' for Raspberry Pi
|
||||||
|
|
||||||
|
monitored_conditions
|
||||||
|
*Optional
|
||||||
|
Conditions to monitor. Available conditions are temperature and humidity.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.const import TEMP_FAHRENHEIT
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
# update this requirement to upstream as soon as it supports python3
|
||||||
|
REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/' +
|
||||||
|
'4101340de8d2457dd194bca1e8d11cbfc237e919.zip']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
SENSOR_TYPES = {
|
||||||
|
'temperature': ['Temperature', ''],
|
||||||
|
'humidity': ['Humidity', '%']
|
||||||
|
}
|
||||||
|
# Return cached results if last scan was less then this time ago
|
||||||
|
# DHT11 is able to deliver data once per second, DHT22 once every two
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Get the DHT sensor. """
|
||||||
|
|
||||||
|
try:
|
||||||
|
import Adafruit_DHT
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unable to import Adafruit_DHT. "
|
||||||
|
"Did you maybe not install the 'Adafruit_DHT' package?")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
|
||||||
|
unit = hass.config.temperature_unit
|
||||||
|
available_sensors = {
|
||||||
|
"DHT11": Adafruit_DHT.DHT11,
|
||||||
|
"DHT22": Adafruit_DHT.DHT22,
|
||||||
|
"AM2302": Adafruit_DHT.AM2302
|
||||||
|
}
|
||||||
|
sensor = available_sensors[config['sensor']]
|
||||||
|
|
||||||
|
pin = config['pin']
|
||||||
|
|
||||||
|
if not sensor or not pin:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Config error "
|
||||||
|
"Please check your settings for DHT, sensor not supported.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = DHTClient(Adafruit_DHT, sensor, pin)
|
||||||
|
dev = []
|
||||||
|
try:
|
||||||
|
for variable in config['monitored_conditions']:
|
||||||
|
if variable not in SENSOR_TYPES:
|
||||||
|
_LOGGER.error('Sensor type: "%s" does not exist', variable)
|
||||||
|
else:
|
||||||
|
dev.append(DHTSensor(data, variable, unit))
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
add_devices(dev)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class DHTSensor(Entity):
|
||||||
|
|
||||||
|
""" Implements an DHT sensor. """
|
||||||
|
|
||||||
|
def __init__(self, dht_client, sensor_type, temp_unit):
|
||||||
|
self.client_name = 'DHT sensor'
|
||||||
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
|
self.dht_client = dht_client
|
||||||
|
self.temp_unit = temp_unit
|
||||||
|
self.type = sensor_type
|
||||||
|
self._state = None
|
||||||
|
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return '{} {}'.format(self.client_name, self._name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the device. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement of this entity, if any. """
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data from the DHT and updates the states. """
|
||||||
|
|
||||||
|
self.dht_client.update()
|
||||||
|
data = self.dht_client.data
|
||||||
|
|
||||||
|
if self.type == 'temperature':
|
||||||
|
self._state = round(data['temperature'], 1)
|
||||||
|
if self.temp_unit == TEMP_FAHRENHEIT:
|
||||||
|
self._state = round(data['temperature'] * 1.8 + 32, 1)
|
||||||
|
elif self.type == 'humidity':
|
||||||
|
self._state = round(data['humidity'], 1)
|
||||||
|
|
||||||
|
|
||||||
|
class DHTClient(object):
|
||||||
|
|
||||||
|
""" Gets the latest data from the DHT sensor. """
|
||||||
|
|
||||||
|
def __init__(self, adafruit_dht, sensor, pin):
|
||||||
|
self.adafruit_dht = adafruit_dht
|
||||||
|
self.sensor = sensor
|
||||||
|
self.pin = pin
|
||||||
|
self.data = dict()
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
def update(self):
|
||||||
|
""" Gets the latest data the DHT sensor. """
|
||||||
|
humidity, temperature = self.adafruit_dht.read_retry(self.sensor,
|
||||||
|
self.pin)
|
||||||
|
if temperature:
|
||||||
|
self.data['temperature'] = temperature
|
||||||
|
if humidity:
|
||||||
|
self.data['humidity'] = humidity
|
@ -49,7 +49,7 @@ Details for the API : https://developer.forecast.io/docs/v2
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
REQUIREMENTS = ['python-forecastio>=1.3.3']
|
REQUIREMENTS = ['python-forecastio==1.3.3']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import forecastio
|
import forecastio
|
||||||
|
94
homeassistant/components/sensor/mqtt.py
Normal file
94
homeassistant/components/sensor/mqtt.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
homeassistant.components.sensor.mqtt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure a MQTT sensor.
|
||||||
|
|
||||||
|
This generic sensor implementation uses the MQTT message payload
|
||||||
|
as the sensor value. If messages in this state_topic are published
|
||||||
|
with RETAIN flag, the sensor will receive an instant update with
|
||||||
|
last known value. Otherwise, the initial state will be undefined.
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: mqtt
|
||||||
|
name: "MQTT Sensor"
|
||||||
|
state_topic: "home/bedroom/temperature"
|
||||||
|
unit_of_measurement: "ºC"
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
name
|
||||||
|
*Optional
|
||||||
|
The name of the sensor. Default is 'MQTT Sensor'.
|
||||||
|
|
||||||
|
state_topic
|
||||||
|
*Required
|
||||||
|
The MQTT topic subscribed to receive sensor values.
|
||||||
|
|
||||||
|
unit_of_measurement
|
||||||
|
*Optional
|
||||||
|
Defines the units of measurement of the sensor, if any.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
import homeassistant.components.mqtt as mqtt
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_NAME = "MQTT Sensor"
|
||||||
|
|
||||||
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Add MQTT Sensor """
|
||||||
|
|
||||||
|
if config.get('state_topic') is None:
|
||||||
|
_LOGGER.error("Missing required variable: state_topic")
|
||||||
|
return False
|
||||||
|
|
||||||
|
add_devices_callback([MqttSensor(
|
||||||
|
hass,
|
||||||
|
config.get('name', DEFAULT_NAME),
|
||||||
|
config.get('state_topic'),
|
||||||
|
config.get('unit_of_measurement'))])
|
||||||
|
|
||||||
|
|
||||||
|
class MqttSensor(Entity):
|
||||||
|
""" Represents a sensor that can be updated using MQTT """
|
||||||
|
def __init__(self, hass, name, state_topic, unit_of_measurement):
|
||||||
|
self._state = "-"
|
||||||
|
self._hass = hass
|
||||||
|
self._name = name
|
||||||
|
self._state_topic = state_topic
|
||||||
|
self._unit_of_measurement = unit_of_measurement
|
||||||
|
|
||||||
|
def message_received(topic, payload, qos):
|
||||||
|
""" A new MQTT message has been received. """
|
||||||
|
self._state = payload
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
mqtt.subscribe(hass, self._state_topic, message_received)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the sensor """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit this state is expressed in. """
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the entity. """
|
||||||
|
return self._state
|
@ -36,8 +36,8 @@ ATTR_NODE_ID = "node_id"
|
|||||||
ATTR_CHILD_ID = "child_id"
|
ATTR_CHILD_ID = "child_id"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/' +
|
||||||
'#egg=pymysensors-0.1']
|
'35b87d880147a34107da0d40cb815d75e6cb4af7.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -48,7 +48,7 @@ from homeassistant.util import Throttle
|
|||||||
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['pyowm>=2.2.1']
|
REQUIREMENTS = ['pyowm==2.2.1']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'weather': ['Condition', ''],
|
'weather': ['Condition', ''],
|
||||||
|
@ -26,8 +26,8 @@ from collections import OrderedDict
|
|||||||
from homeassistant.const import (TEMP_CELCIUS)
|
from homeassistant.const import (TEMP_CELCIUS)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' +
|
||||||
'#RFXtrx>=0.15']
|
'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip']
|
||||||
|
|
||||||
DATA_TYPES = OrderedDict([
|
DATA_TYPES = OrderedDict([
|
||||||
('Temperature', TEMP_CELCIUS),
|
('Temperature', TEMP_CELCIUS),
|
||||||
|
133
homeassistant/components/sensor/rpi_gpio.py
Normal file
133
homeassistant/components/sensor/rpi_gpio.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
homeassistant.components.sensor.rpi_gpio
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure a binary state sensor using RPi GPIO.
|
||||||
|
Note: To use RPi GPIO, Home Assistant must be run as root.
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
platform: rpi_gpio
|
||||||
|
pull_mode: "UP"
|
||||||
|
value_high: "Active"
|
||||||
|
value_low: "Inactive"
|
||||||
|
ports:
|
||||||
|
11: PIR Office
|
||||||
|
12: PIR Bedroom
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
pull_mode
|
||||||
|
*Optional
|
||||||
|
The internal pull to use (UP or DOWN). Default is UP.
|
||||||
|
|
||||||
|
value_high
|
||||||
|
*Optional
|
||||||
|
The value of the sensor when the port is HIGH. Default is "HIGH".
|
||||||
|
|
||||||
|
value_low
|
||||||
|
*Optional
|
||||||
|
The value of the sensor when the port is LOW. Default is "LOW".
|
||||||
|
|
||||||
|
bouncetime
|
||||||
|
*Optional
|
||||||
|
The time in milliseconds for port debouncing. Default is 50ms.
|
||||||
|
|
||||||
|
ports
|
||||||
|
*Required
|
||||||
|
An array specifying the GPIO ports to use and the name to use in the frontend.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
try:
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
except ImportError:
|
||||||
|
GPIO = None
|
||||||
|
from homeassistant.const import (DEVICE_DEFAULT_NAME,
|
||||||
|
EVENT_HOMEASSISTANT_START,
|
||||||
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
|
DEFAULT_PULL_MODE = "UP"
|
||||||
|
DEFAULT_VALUE_HIGH = "HIGH"
|
||||||
|
DEFAULT_VALUE_LOW = "LOW"
|
||||||
|
DEFAULT_BOUNCETIME = 50
|
||||||
|
|
||||||
|
REQUIREMENTS = ['RPi.GPIO==0.5.11']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the Raspberry PI GPIO ports. """
|
||||||
|
if GPIO is None:
|
||||||
|
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
|
||||||
|
return
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
|
||||||
|
value_high = config.get('value_high', DEFAULT_VALUE_HIGH)
|
||||||
|
value_low = config.get('value_low', DEFAULT_VALUE_LOW)
|
||||||
|
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
|
||||||
|
ports = config.get('ports')
|
||||||
|
for port_num, port_name in ports.items():
|
||||||
|
sensors.append(RPiGPIOSensor(
|
||||||
|
port_name, port_num, pull_mode,
|
||||||
|
value_high, value_low, bouncetime))
|
||||||
|
add_devices(sensors)
|
||||||
|
|
||||||
|
def cleanup_gpio(event):
|
||||||
|
""" Stuff to do before stop home assistant. """
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
def prepare_gpio(event):
|
||||||
|
""" Stuff to do when home assistant starts. """
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
|
class RPiGPIOSensor(Entity):
|
||||||
|
""" Sets up the Raspberry PI GPIO ports. """
|
||||||
|
def __init__(self, port_name, port_num, pull_mode,
|
||||||
|
value_high, value_low, bouncetime):
|
||||||
|
# pylint: disable=no-member
|
||||||
|
self._name = port_name or DEVICE_DEFAULT_NAME
|
||||||
|
self._port = port_num
|
||||||
|
self._pull = GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP
|
||||||
|
self._vhigh = value_high
|
||||||
|
self._vlow = value_low
|
||||||
|
self._bouncetime = bouncetime
|
||||||
|
GPIO.setup(self._port, GPIO.IN, pull_up_down=self._pull)
|
||||||
|
self._state = self._vhigh if GPIO.input(self._port) else self._vlow
|
||||||
|
|
||||||
|
def edge_callback(channel):
|
||||||
|
""" port changed state """
|
||||||
|
# pylint: disable=no-member
|
||||||
|
self._state = self._vhigh if GPIO.input(channel) else self._vlow
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
GPIO.add_event_detect(
|
||||||
|
self._port,
|
||||||
|
GPIO.BOTH,
|
||||||
|
callback=edge_callback,
|
||||||
|
bouncetime=self._bouncetime)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the sensor """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
""" Returns the state of the entity. """
|
||||||
|
return self._state
|
@ -66,7 +66,7 @@ import homeassistant.util.dt as dt_util
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
|
|
||||||
REQUIREMENTS = ['psutil>=3.0.0']
|
REQUIREMENTS = ['psutil==3.0.0']
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'disk_use_percent': ['Disk Use', '%'],
|
'disk_use_percent': ['Disk Use', '%'],
|
||||||
'disk_use': ['Disk Use', 'GiB'],
|
'disk_use': ['Disk Use', 'GiB'],
|
||||||
|
@ -35,7 +35,7 @@ import homeassistant.util as util
|
|||||||
|
|
||||||
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
|
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
|
||||||
|
|
||||||
REQUIREMENTS = ['tellcore-py>=1.0.4']
|
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -18,7 +18,8 @@ from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/master.zip']
|
REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/' +
|
||||||
|
'3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -67,7 +67,7 @@ from transmissionrpc.error import TransmissionError
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
REQUIREMENTS = ['transmissionrpc>=0.11']
|
REQUIREMENTS = ['transmissionrpc==0.11']
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'current_status': ['Status', ''],
|
'current_status': ['Status', ''],
|
||||||
'download_speed': ['Down Speed', 'MB/s'],
|
'download_speed': ['Down Speed', 'MB/s'],
|
||||||
|
@ -8,8 +8,8 @@ import logging
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
|
||||||
'#pywink>=0.1']
|
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -31,7 +31,7 @@ from homeassistant.helpers.entity import Entity
|
|||||||
from homeassistant.components.scheduler import ServiceEventListener
|
from homeassistant.components.scheduler import ServiceEventListener
|
||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['astral>=0.8.1']
|
REQUIREMENTS = ['astral==0.8.1']
|
||||||
DOMAIN = "sun"
|
DOMAIN = "sun"
|
||||||
ENTITY_ID = "sun.sun"
|
ENTITY_ID = "sun.sun"
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD,\
|
|||||||
DEFAULT_USERNAME = 'admin'
|
DEFAULT_USERNAME = 'admin'
|
||||||
DEFAULT_PASSWORD = '1234'
|
DEFAULT_PASSWORD = '1234'
|
||||||
DEVICE_DEFAULT_NAME = 'Edimax Smart Plug'
|
DEVICE_DEFAULT_NAME = 'Edimax Smart Plug'
|
||||||
REQUIREMENTS = ['https://github.com/rkabadi/pyedimax/archive/master.zip']
|
REQUIREMENTS = ['https://github.com/rkabadi/pyedimax/archive/' +
|
||||||
|
'365301ce3ff26129a7910c501ead09ea625f3700.zip']
|
||||||
|
|
||||||
# setup logger
|
# setup logger
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -49,7 +49,7 @@ except ImportError:
|
|||||||
hikvision.api = None
|
hikvision.api = None
|
||||||
|
|
||||||
_LOGGING = logging.getLogger(__name__)
|
_LOGGING = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['hikvision>=0.4']
|
REQUIREMENTS = ['hikvision==0.4']
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
|
153
homeassistant/components/switch/mqtt.py
Normal file
153
homeassistant/components/switch/mqtt.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
homeassistant.components.switch.mqtt
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows to configure a MQTT switch.
|
||||||
|
|
||||||
|
In an ideal scenario, the MQTT device will have a state topic to publish
|
||||||
|
state changes. If these messages are published with RETAIN flag, the MQTT
|
||||||
|
switch will receive an instant state update after subscription and will
|
||||||
|
start with correct state. Otherwise, the initial state of the switch will
|
||||||
|
be false/off.
|
||||||
|
|
||||||
|
When a state topic is not available, the switch will work in optimistic mode.
|
||||||
|
In this mode, the switch will immediately change state after every command.
|
||||||
|
Otherwise, the switch will wait for state confirmation from device
|
||||||
|
(message from state_topic).
|
||||||
|
|
||||||
|
Optimistic mode can be forced, even if state topic is available.
|
||||||
|
Try to enable it, if experiencing incorrect switch operation.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
switch:
|
||||||
|
platform: mqtt
|
||||||
|
name: "Bedroom Switch"
|
||||||
|
state_topic: "home/bedroom/switch1"
|
||||||
|
command_topic: "home/bedroom/switch1/set"
|
||||||
|
payload_on: "ON"
|
||||||
|
payload_off: "OFF"
|
||||||
|
optimistic: false
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
|
||||||
|
name
|
||||||
|
*Optional
|
||||||
|
The name of the switch. Default is 'MQTT Switch'.
|
||||||
|
|
||||||
|
state_topic
|
||||||
|
*Optional
|
||||||
|
The MQTT topic subscribed to receive state updates.
|
||||||
|
If not specified, optimistic mode will be forced.
|
||||||
|
|
||||||
|
command_topic
|
||||||
|
*Required
|
||||||
|
The MQTT topic to publish commands to change the switch state.
|
||||||
|
|
||||||
|
payload_on
|
||||||
|
*Optional
|
||||||
|
The payload that represents enabled state. Default is "ON".
|
||||||
|
|
||||||
|
payload_off
|
||||||
|
*Optional
|
||||||
|
The payload that represents disabled state. Default is "OFF".
|
||||||
|
|
||||||
|
optimistic
|
||||||
|
*Optional
|
||||||
|
Flag that defines if switch works in optimistic mode. Default is false.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import homeassistant.components.mqtt as mqtt
|
||||||
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_NAME = "MQTT Switch"
|
||||||
|
DEFAULT_PAYLOAD_ON = "ON"
|
||||||
|
DEFAULT_PAYLOAD_OFF = "OFF"
|
||||||
|
DEFAULT_OPTIMISTIC = False
|
||||||
|
|
||||||
|
DEPENDENCIES = ['mqtt']
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Add MQTT Switch """
|
||||||
|
|
||||||
|
if config.get('command_topic') is None:
|
||||||
|
_LOGGER.error("Missing required variable: command_topic")
|
||||||
|
return False
|
||||||
|
|
||||||
|
add_devices_callback([MqttSwitch(
|
||||||
|
hass,
|
||||||
|
config.get('name', DEFAULT_NAME),
|
||||||
|
config.get('state_topic'),
|
||||||
|
config.get('command_topic'),
|
||||||
|
config.get('payload_on', DEFAULT_PAYLOAD_ON),
|
||||||
|
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
|
||||||
|
config.get('optimistic', DEFAULT_OPTIMISTIC))])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
|
class MqttSwitch(SwitchDevice):
|
||||||
|
""" Represents a switch that can be togggled using MQTT """
|
||||||
|
def __init__(self, hass, name, state_topic, command_topic,
|
||||||
|
payload_on, payload_off, optimistic):
|
||||||
|
self._state = False
|
||||||
|
self._hass = hass
|
||||||
|
self._name = name
|
||||||
|
self._state_topic = state_topic
|
||||||
|
self._command_topic = command_topic
|
||||||
|
self._payload_on = payload_on
|
||||||
|
self._payload_off = payload_off
|
||||||
|
self._optimistic = optimistic
|
||||||
|
|
||||||
|
def message_received(topic, payload, qos):
|
||||||
|
""" A new MQTT message has been received. """
|
||||||
|
if payload == self._payload_on:
|
||||||
|
self._state = True
|
||||||
|
self.update_ha_state()
|
||||||
|
elif payload == self._payload_off:
|
||||||
|
self._state = False
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
if self._state_topic is None:
|
||||||
|
# force optimistic mode
|
||||||
|
self._optimistic = True
|
||||||
|
else:
|
||||||
|
# subscribe the state_topic
|
||||||
|
mqtt.subscribe(hass, self._state_topic, message_received)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the switch """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turn the device on. """
|
||||||
|
mqtt.publish(self.hass, self._command_topic, self._payload_on)
|
||||||
|
if self._optimistic:
|
||||||
|
# optimistically assume that switch has changed state
|
||||||
|
self._state = True
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Turn the device off. """
|
||||||
|
mqtt.publish(self.hass, self._command_topic, self._payload_off)
|
||||||
|
if self._optimistic:
|
||||||
|
# optimistically assume that switch has changed state
|
||||||
|
self._state = False
|
||||||
|
self.update_ha_state()
|
@ -2,21 +2,28 @@
|
|||||||
homeassistant.components.switch.rpi_gpio
|
homeassistant.components.switch.rpi_gpio
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Allows to control the GPIO pins of a Raspberry Pi.
|
Allows to control the GPIO pins of a Raspberry Pi.
|
||||||
|
Note: To use RPi GPIO, Home Assistant must be run as root.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
platform: rpi_gpio
|
platform: rpi_gpio
|
||||||
|
invert_logic: false
|
||||||
ports:
|
ports:
|
||||||
11: Fan Office
|
11: Fan Office
|
||||||
12: Light Desk
|
12: Light Desk
|
||||||
|
|
||||||
Variables:
|
Variables:
|
||||||
|
|
||||||
|
invert_logic
|
||||||
|
*Optional
|
||||||
|
If true, inverts the output logic to ACTIVE LOW. Default is false (ACTIVE HIGH)
|
||||||
|
|
||||||
ports
|
ports
|
||||||
*Required
|
*Required
|
||||||
An array specifying the GPIO ports to use and the name to use in the frontend.
|
An array specifying the GPIO ports to use and the name to use in the frontend.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
try:
|
try:
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
@ -27,7 +34,9 @@ from homeassistant.const import (DEVICE_DEFAULT_NAME,
|
|||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
REQUIREMENTS = ['RPi.GPIO>=0.5.11']
|
DEFAULT_INVERT_LOGIC = False
|
||||||
|
|
||||||
|
REQUIREMENTS = ['RPi.GPIO==0.5.11']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -37,11 +46,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if GPIO is None:
|
if GPIO is None:
|
||||||
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
|
_LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.')
|
||||||
return
|
return
|
||||||
|
# pylint: disable=no-member
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
switches = []
|
switches = []
|
||||||
|
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
|
||||||
ports = config.get('ports')
|
ports = config.get('ports')
|
||||||
for port_num, port_name in ports.items():
|
for port_num, port_name in ports.items():
|
||||||
switches.append(RPiGPIOSwitch(port_name, port_num))
|
switches.append(RPiGPIOSwitch(port_name, port_num, invert_logic))
|
||||||
add_devices(switches)
|
add_devices(switches)
|
||||||
|
|
||||||
def cleanup_gpio(event):
|
def cleanup_gpio(event):
|
||||||
@ -59,10 +71,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
class RPiGPIOSwitch(ToggleEntity):
|
class RPiGPIOSwitch(ToggleEntity):
|
||||||
""" Represents a port that can be toggled using Raspberry Pi GPIO. """
|
""" Represents a port that can be toggled using Raspberry Pi GPIO. """
|
||||||
|
|
||||||
def __init__(self, name, gpio):
|
def __init__(self, name, gpio, invert_logic):
|
||||||
self._name = name or DEVICE_DEFAULT_NAME
|
self._name = name or DEVICE_DEFAULT_NAME
|
||||||
self._state = False
|
|
||||||
self._gpio = gpio
|
self._gpio = gpio
|
||||||
|
self._active_state = not invert_logic
|
||||||
|
self._state = not self._active_state
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
GPIO.setup(gpio, GPIO.OUT)
|
GPIO.setup(gpio, GPIO.OUT)
|
||||||
|
|
||||||
@ -83,13 +96,13 @@ class RPiGPIOSwitch(ToggleEntity):
|
|||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" Turn the device on. """
|
""" Turn the device on. """
|
||||||
if self._switch(True):
|
if self._switch(self._active_state):
|
||||||
self._state = True
|
self._state = True
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
""" Turn the device off. """
|
""" Turn the device off. """
|
||||||
if self._switch(False):
|
if self._switch(not self._active_state):
|
||||||
self._state = False
|
self._state = False
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import tellcore.constants as tellcore_constants
|
|||||||
|
|
||||||
SINGAL_REPETITIONS = 1
|
SINGAL_REPETITIONS = 1
|
||||||
|
|
||||||
REQUIREMENTS = ['tellcore-py>=1.0.4']
|
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -48,7 +48,7 @@ from transmissionrpc.error import TransmissionError
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
_LOGGING = logging.getLogger(__name__)
|
_LOGGING = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['transmissionrpc>=0.11']
|
REQUIREMENTS = ['transmissionrpc==0.11']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -8,7 +8,7 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
REQUIREMENTS = ['pywemo>=0.1']
|
REQUIREMENTS = ['pywemo==0.2']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
@ -18,7 +18,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
import pywemo.discovery as discovery
|
import pywemo.discovery as discovery
|
||||||
|
|
||||||
if discovery_info is not None:
|
if discovery_info is not None:
|
||||||
device = discovery.device_from_description(discovery_info)
|
device = discovery.device_from_description(discovery_info[2])
|
||||||
|
|
||||||
if device:
|
if device:
|
||||||
add_devices_callback([WemoSwitch(device)])
|
add_devices_callback([WemoSwitch(device)])
|
||||||
|
@ -9,8 +9,8 @@ import logging
|
|||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
|
||||||
'#pywink>=0.1']
|
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -10,6 +10,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
|||||||
|
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.temperature import convert
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS)
|
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, TEMP_CELCIUS)
|
||||||
|
|
||||||
@ -86,7 +87,9 @@ def setup(hass, config):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for thermostat in target_thermostats:
|
for thermostat in target_thermostats:
|
||||||
thermostat.set_temperature(temperature)
|
thermostat.set_temperature(convert(
|
||||||
|
temperature, hass.config.temperature_unit,
|
||||||
|
thermostat.unit_of_measurement))
|
||||||
|
|
||||||
for thermostat in target_thermostats:
|
for thermostat in target_thermostats:
|
||||||
thermostat.update_ha_state(True)
|
thermostat.update_ha_state(True)
|
||||||
@ -118,9 +121,20 @@ class ThermostatDevice(Entity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns optional state attributes. """
|
""" Returns optional state attributes. """
|
||||||
|
|
||||||
|
thermostat_unit = self.unit_of_measurement
|
||||||
|
user_unit = self.hass.config.temperature_unit
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
ATTR_CURRENT_TEMPERATURE: self.hass.config.temperature(
|
ATTR_CURRENT_TEMPERATURE: round(convert(self.current_temperature,
|
||||||
self.current_temperature, self.unit_of_measurement)[0]
|
thermostat_unit,
|
||||||
|
user_unit), 1),
|
||||||
|
ATTR_MIN_TEMP: round(convert(self.min_temp,
|
||||||
|
thermostat_unit,
|
||||||
|
user_unit), 0),
|
||||||
|
ATTR_MAX_TEMP: round(convert(self.max_temp,
|
||||||
|
thermostat_unit,
|
||||||
|
user_unit), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
is_away = self.is_away_mode_on
|
is_away = self.is_away_mode_on
|
||||||
@ -133,11 +147,13 @@ class ThermostatDevice(Entity):
|
|||||||
if device_attr is not None:
|
if device_attr is not None:
|
||||||
data.update(device_attr)
|
data.update(device_attr)
|
||||||
|
|
||||||
data[ATTR_MIN_TEMP] = self.min_temp
|
|
||||||
data[ATTR_MAX_TEMP] = self.max_temp
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
""" Unit of measurement this thermostat expresses itself in. """
|
||||||
|
return NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
""" Returns the current temperature. """
|
""" Returns the current temperature. """
|
||||||
@ -171,9 +187,9 @@ class ThermostatDevice(Entity):
|
|||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
""" Return minimum temperature. """
|
""" Return minimum temperature. """
|
||||||
return self.hass.config.temperature(7, TEMP_CELCIUS)[0]
|
return convert(7, TEMP_CELCIUS, self.unit_of_measurement)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
""" Return maxmum temperature. """
|
""" Return maxmum temperature. """
|
||||||
return self.hass.config.temperature(35, TEMP_CELCIUS)[0]
|
return convert(35, TEMP_CELCIUS, self.unit_of_measurement)
|
||||||
|
@ -6,7 +6,7 @@ import logging
|
|||||||
from homeassistant.components.thermostat import ThermostatDevice
|
from homeassistant.components.thermostat import ThermostatDevice
|
||||||
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
|
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
|
||||||
|
|
||||||
REQUIREMENTS = ['python-nest>=2.3.1']
|
REQUIREMENTS = ['python-nest==2.4.0']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
@ -50,11 +50,19 @@ class NestThermostat(ThermostatDevice):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
""" Returns the name of the nest, if any. """
|
""" Returns the name of the nest, if any. """
|
||||||
return self.device.name
|
location = self.device.where
|
||||||
|
name = self.device.name
|
||||||
|
if location is None:
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
if name == '':
|
||||||
|
return location.capitalize()
|
||||||
|
else:
|
||||||
|
return location.capitalize() + '(' + name + ')'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
""" Returns the unit of measurement. """
|
""" Unit of measurement this thermostat expresses itself in. """
|
||||||
return TEMP_CELCIUS
|
return TEMP_CELCIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -109,6 +117,24 @@ class NestThermostat(ThermostatDevice):
|
|||||||
""" Turns away off. """
|
""" Turns away off. """
|
||||||
self.structure.away = False
|
self.structure.away = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self):
|
||||||
|
""" Identifies min_temp in Nest API or defaults if not available. """
|
||||||
|
temp = self.device.away_temperature.low
|
||||||
|
if temp is None:
|
||||||
|
return super().min_temp
|
||||||
|
else:
|
||||||
|
return temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self):
|
||||||
|
""" Identifies mxn_temp in Nest API or defaults if not available. """
|
||||||
|
temp = self.device.away_temperature.high
|
||||||
|
if temp is None:
|
||||||
|
return super().max_temp
|
||||||
|
else:
|
||||||
|
return temp
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Python-nest has its own mechanism for staying up to date. """
|
""" Python-nest has its own mechanism for staying up to date. """
|
||||||
pass
|
pass
|
||||||
|
@ -61,7 +61,8 @@ DISCOVER_SWITCHES = 'verisure.switches'
|
|||||||
|
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = [
|
REQUIREMENTS = [
|
||||||
'https://github.com/persandstrom/python-verisure/archive/master.zip'
|
'https://github.com/persandstrom/python-verisure/archive/' +
|
||||||
|
'9873c4527f01b1ba1f72ae60f7f35854390d59be.zip'
|
||||||
]
|
]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -16,8 +16,8 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "wink"
|
DOMAIN = "wink"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
|
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/' +
|
||||||
'#pywink>=0.1']
|
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip']
|
||||||
|
|
||||||
DISCOVER_LIGHTS = "wink.lights"
|
DISCOVER_LIGHTS = "wink.lights"
|
||||||
DISCOVER_SWITCHES = "wink.switches"
|
DISCOVER_SWITCHES = "wink.switches"
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
DOMAIN = "zwave"
|
DOMAIN = "zwave"
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
REQUIREMENTS = ['pydispatcher>=2.0.5']
|
REQUIREMENTS = ['pydispatcher==2.0.5']
|
||||||
|
|
||||||
CONF_USB_STICK_PATH = "usb_path"
|
CONF_USB_STICK_PATH = "usb_path"
|
||||||
DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick"
|
DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick"
|
||||||
|
@ -7,7 +7,7 @@ Module to help with parsing and generating configuration files.
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||||
CONF_TIME_ZONE)
|
CONF_TIME_ZONE)
|
||||||
@ -16,6 +16,7 @@ import homeassistant.util.location as loc_util
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
YAML_CONFIG_FILE = 'configuration.yaml'
|
YAML_CONFIG_FILE = 'configuration.yaml'
|
||||||
|
CONFIG_DIR_NAME = '.homeassistant'
|
||||||
|
|
||||||
DEFAULT_CONFIG = (
|
DEFAULT_CONFIG = (
|
||||||
# Tuples (attribute, default, auto detect property, description)
|
# Tuples (attribute, default, auto detect property, description)
|
||||||
@ -28,8 +29,22 @@ DEFAULT_CONFIG = (
|
|||||||
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
|
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
|
||||||
'pedia.org/wiki/List_of_tz_database_time_zones'),
|
'pedia.org/wiki/List_of_tz_database_time_zones'),
|
||||||
)
|
)
|
||||||
DEFAULT_COMPONENTS = (
|
DEFAULT_COMPONENTS = {
|
||||||
'discovery', 'frontend', 'conversation', 'history', 'logbook', 'sun')
|
'introduction': 'Show links to resources in log and frontend',
|
||||||
|
'frontend': 'Enables the frontend',
|
||||||
|
'discovery': 'Discover some devices automatically',
|
||||||
|
'conversation': 'Allows you to issue voice commands from the frontend',
|
||||||
|
'history': 'Enables support for tracking state changes over time.',
|
||||||
|
'logbook': 'View all events in a logbook',
|
||||||
|
'sun': 'Track the sun',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_config_dir():
|
||||||
|
""" Put together the default configuration directory based on OS. """
|
||||||
|
data_dir = os.getenv('APPDATA') if os.name == "nt" \
|
||||||
|
else os.path.expanduser('~')
|
||||||
|
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):
|
||||||
@ -39,7 +54,8 @@ def ensure_config_exists(config_dir, detect_location=True):
|
|||||||
config_path = find_config_file(config_dir)
|
config_path = find_config_file(config_dir)
|
||||||
|
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
_LOGGER.info("Unable to find configuration. Creating default one")
|
print("Unable to find configuration. Creating default one at",
|
||||||
|
config_dir)
|
||||||
config_path = create_default_config(config_dir, detect_location)
|
config_path = create_default_config(config_dir, detect_location)
|
||||||
|
|
||||||
return config_path
|
return config_path
|
||||||
@ -78,15 +94,14 @@ def create_default_config(config_dir, detect_location=True):
|
|||||||
|
|
||||||
config_file.write("\n")
|
config_file.write("\n")
|
||||||
|
|
||||||
for component in DEFAULT_COMPONENTS:
|
for component, description in DEFAULT_COMPONENTS.items():
|
||||||
|
config_file.write("# {}\n".format(description))
|
||||||
config_file.write("{}:\n\n".format(component))
|
config_file.write("{}:\n\n".format(component))
|
||||||
|
|
||||||
return config_path
|
return config_path
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
_LOGGER.exception(
|
print('Unable to create default configuration file', config_path)
|
||||||
'Unable to write default configuration file %s', config_path)
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
""" Constants used by Home Assistant components. """
|
""" Constants used by Home Assistant components. """
|
||||||
|
|
||||||
|
__version__ = "0.7.0"
|
||||||
|
|
||||||
# Can be used to specify a catch all when registering state or event listeners.
|
# Can be used to specify a catch all when registering state or event listeners.
|
||||||
MATCH_ALL = '*'
|
MATCH_ALL = '*'
|
||||||
|
|
||||||
|
@ -21,9 +21,12 @@ from homeassistant.const import (
|
|||||||
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
|
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
|
||||||
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME)
|
||||||
|
from homeassistant.exceptions import (
|
||||||
|
HomeAssistantError, InvalidEntityFormatError)
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
import homeassistant.helpers.temperature as temp_helper
|
import homeassistant.helpers.temperature as temp_helper
|
||||||
|
from homeassistant.config import get_default_config_dir
|
||||||
|
|
||||||
DOMAIN = "homeassistant"
|
DOMAIN = "homeassistant"
|
||||||
|
|
||||||
@ -660,7 +663,7 @@ class Config(object):
|
|||||||
self.api = None
|
self.api = None
|
||||||
|
|
||||||
# Directory that holds the configuration
|
# Directory that holds the configuration
|
||||||
self.config_dir = os.path.join(os.getcwd(), 'config')
|
self.config_dir = get_default_config_dir()
|
||||||
|
|
||||||
def path(self, *path):
|
def path(self, *path):
|
||||||
""" Returns path to the file within the config dir. """
|
""" Returns path to the file within the config dir. """
|
||||||
@ -695,21 +698,6 @@ class Config(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantError(Exception):
|
|
||||||
""" General Home Assistant exception occured. """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidEntityFormatError(HomeAssistantError):
|
|
||||||
""" When an invalid formatted entity is encountered. """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoEntitySpecifiedError(HomeAssistantError):
|
|
||||||
""" When no entity is specified. """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def create_timer(hass, interval=TIMER_INTERVAL):
|
def create_timer(hass, interval=TIMER_INTERVAL):
|
||||||
""" Creates a timer. Timer will start on HOMEASSISTANT_START. """
|
""" Creates a timer. Timer will start on HOMEASSISTANT_START. """
|
||||||
# We want to be able to fire every time a minute starts (seconds=0).
|
# We want to be able to fire every time a minute starts (seconds=0).
|
||||||
|
16
homeassistant/exceptions.py
Normal file
16
homeassistant/exceptions.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
""" Exceptions used by Home Assistant """
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistantError(Exception):
|
||||||
|
""" General Home Assistant exception occured. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidEntityFormatError(HomeAssistantError):
|
||||||
|
""" When an invalid formatted entity is encountered. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoEntitySpecifiedError(HomeAssistantError):
|
||||||
|
""" When no entity is specified. """
|
||||||
|
pass
|
@ -7,7 +7,7 @@ Provides ABC for entities in HA.
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from homeassistant.core import NoEntitySpecifiedError
|
from homeassistant.exceptions import NoEntitySpecifiedError
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN,
|
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN,
|
||||||
|
@ -166,9 +166,10 @@ def load_order_components(components):
|
|||||||
key=lambda order: 'group' in order):
|
key=lambda order: 'group' in order):
|
||||||
load_order.update(comp_load_order)
|
load_order.update(comp_load_order)
|
||||||
|
|
||||||
# Push recorder to first place in load order
|
# Push some to first place in load order
|
||||||
if 'recorder' in load_order:
|
for comp in ('recorder', 'introduction'):
|
||||||
load_order.promote('recorder')
|
if comp in load_order:
|
||||||
|
load_order.promote(comp)
|
||||||
|
|
||||||
return load_order
|
return load_order
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import urllib.parse
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -84,12 +85,12 @@ class API(object):
|
|||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
_LOGGER.exception("Error connecting to server")
|
_LOGGER.exception("Error connecting to server")
|
||||||
raise ha.HomeAssistantError("Error connecting to server")
|
raise HomeAssistantError("Error connecting to server")
|
||||||
|
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
error = "Timeout when talking to {}".format(self.host)
|
error = "Timeout when talking to {}".format(self.host)
|
||||||
_LOGGER.exception(error)
|
_LOGGER.exception(error)
|
||||||
raise ha.HomeAssistantError(error)
|
raise HomeAssistantError(error)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "API({}, {}, {})".format(
|
return "API({}, {}, {})".format(
|
||||||
@ -102,7 +103,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
|
|
||||||
def __init__(self, remote_api, local_api=None):
|
def __init__(self, remote_api, local_api=None):
|
||||||
if not remote_api.validate_api():
|
if not remote_api.validate_api():
|
||||||
raise ha.HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
"Remote API at {}:{} not valid: {}".format(
|
"Remote API at {}:{} not valid: {}".format(
|
||||||
remote_api.host, remote_api.port, remote_api.status))
|
remote_api.host, remote_api.port, remote_api.status))
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
# 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'):
|
||||||
raise ha.HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
'Unable to setup local API to receive events')
|
'Unable to setup local API to receive events')
|
||||||
|
|
||||||
ha.create_timer(self)
|
ha.create_timer(self)
|
||||||
@ -132,7 +133,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
# Setup that events from remote_api get forwarded to local_api
|
# Setup that events from remote_api get forwarded to local_api
|
||||||
# Do this after we fire START, otherwise HTTP is not started
|
# Do this after we fire START, otherwise HTTP is not started
|
||||||
if not connect_remote_events(self.remote_api, self.config.api):
|
if not connect_remote_events(self.remote_api, self.config.api):
|
||||||
raise ha.HomeAssistantError((
|
raise HomeAssistantError((
|
||||||
'Could not setup event forwarding from api {} to '
|
'Could not setup event forwarding from api {} to '
|
||||||
'local api {}').format(self.remote_api, self.config.api))
|
'local api {}').format(self.remote_api, self.config.api))
|
||||||
|
|
||||||
@ -293,7 +294,7 @@ def validate_api(api):
|
|||||||
else:
|
else:
|
||||||
return APIStatus.UNKNOWN
|
return APIStatus.UNKNOWN
|
||||||
|
|
||||||
except ha.HomeAssistantError:
|
except HomeAssistantError:
|
||||||
return APIStatus.CANNOT_CONNECT
|
return APIStatus.CANNOT_CONNECT
|
||||||
|
|
||||||
|
|
||||||
@ -318,7 +319,7 @@ def connect_remote_events(from_api, to_api):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except ha.HomeAssistantError:
|
except HomeAssistantError:
|
||||||
_LOGGER.exception("Error setting up event forwarding")
|
_LOGGER.exception("Error setting up event forwarding")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -342,7 +343,7 @@ def disconnect_remote_events(from_api, to_api):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except ha.HomeAssistantError:
|
except HomeAssistantError:
|
||||||
_LOGGER.exception("Error removing an event forwarder")
|
_LOGGER.exception("Error removing an event forwarder")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -354,7 +355,7 @@ def get_event_listeners(api):
|
|||||||
|
|
||||||
return req.json() if req.status_code == 200 else {}
|
return req.json() if req.status_code == 200 else {}
|
||||||
|
|
||||||
except (ha.HomeAssistantError, ValueError):
|
except (HomeAssistantError, ValueError):
|
||||||
# ValueError if req.json() can't parse the json
|
# ValueError if req.json() can't parse the json
|
||||||
_LOGGER.exception("Unexpected result retrieving event listeners")
|
_LOGGER.exception("Unexpected result retrieving event listeners")
|
||||||
|
|
||||||
@ -371,7 +372,7 @@ def fire_event(api, event_type, data=None):
|
|||||||
_LOGGER.error("Error firing event: %d - %d",
|
_LOGGER.error("Error firing event: %d - %d",
|
||||||
req.status_code, req.text)
|
req.status_code, req.text)
|
||||||
|
|
||||||
except ha.HomeAssistantError:
|
except HomeAssistantError:
|
||||||
_LOGGER.exception("Error firing event")
|
_LOGGER.exception("Error firing event")
|
||||||
|
|
||||||
|
|
||||||
@ -387,7 +388,7 @@ def get_state(api, entity_id):
|
|||||||
return ha.State.from_dict(req.json()) \
|
return ha.State.from_dict(req.json()) \
|
||||||
if req.status_code == 200 else None
|
if req.status_code == 200 else None
|
||||||
|
|
||||||
except (ha.HomeAssistantError, ValueError):
|
except (HomeAssistantError, ValueError):
|
||||||
# ValueError if req.json() can't parse the json
|
# ValueError if req.json() can't parse the json
|
||||||
_LOGGER.exception("Error fetching state")
|
_LOGGER.exception("Error fetching state")
|
||||||
|
|
||||||
@ -404,7 +405,7 @@ def get_states(api):
|
|||||||
return [ha.State.from_dict(item) for
|
return [ha.State.from_dict(item) for
|
||||||
item in req.json()]
|
item in req.json()]
|
||||||
|
|
||||||
except (ha.HomeAssistantError, ValueError, AttributeError):
|
except (HomeAssistantError, ValueError, AttributeError):
|
||||||
# ValueError if req.json() can't parse the json
|
# ValueError if req.json() can't parse the json
|
||||||
_LOGGER.exception("Error fetching states")
|
_LOGGER.exception("Error fetching states")
|
||||||
|
|
||||||
@ -434,7 +435,7 @@ def set_state(api, entity_id, new_state, attributes=None):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except ha.HomeAssistantError:
|
except HomeAssistantError:
|
||||||
_LOGGER.exception("Error setting state")
|
_LOGGER.exception("Error setting state")
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -457,7 +458,7 @@ def get_services(api):
|
|||||||
|
|
||||||
return req.json() if req.status_code == 200 else {}
|
return req.json() if req.status_code == 200 else {}
|
||||||
|
|
||||||
except (ha.HomeAssistantError, ValueError):
|
except (HomeAssistantError, ValueError):
|
||||||
# ValueError if req.json() can't parse the json
|
# ValueError if req.json() can't parse the json
|
||||||
_LOGGER.exception("Got unexpected services result")
|
_LOGGER.exception("Got unexpected services result")
|
||||||
|
|
||||||
@ -475,5 +476,5 @@ def call_service(api, domain, service, service_data=None):
|
|||||||
_LOGGER.error("Error calling service: %d - %s",
|
_LOGGER.error("Error calling service: %d - %s",
|
||||||
req.status_code, req.text)
|
req.status_code, req.text)
|
||||||
|
|
||||||
except ha.HomeAssistantError:
|
except HomeAssistantError:
|
||||||
_LOGGER.exception("Error calling service")
|
_LOGGER.exception("Error calling service")
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
"""Helpers to install PyPi packages."""
|
"""Helpers to install PyPi packages."""
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from . import environment as env
|
|
||||||
|
|
||||||
# If we are not in a virtual environment, install in user space
|
def install_package(package, upgrade=False, target=None):
|
||||||
INSTALL_USER = not env.is_virtual()
|
|
||||||
|
|
||||||
|
|
||||||
def install_package(package, upgrade=False, user=INSTALL_USER):
|
|
||||||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
"""Install a package on PyPi. Accepts pip compatible package strings.
|
||||||
Return boolean if install successfull."""
|
Return boolean if install successfull."""
|
||||||
# Not using 'import pip; pip.main([])' because it breaks the logger
|
# Not using 'import pip; pip.main([])' because it breaks the logger
|
||||||
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
|
||||||
if upgrade:
|
if upgrade:
|
||||||
args.append('--upgrade')
|
args.append('--upgrade')
|
||||||
if user:
|
if target:
|
||||||
args.append('--user')
|
args += ['--target', os.path.abspath(target)]
|
||||||
try:
|
try:
|
||||||
return 0 == subprocess.call(args)
|
return 0 == subprocess.call(args)
|
||||||
except subprocess.SubprocessError:
|
except subprocess.SubprocessError:
|
||||||
|
2
pylintrc
2
pylintrc
@ -1,5 +1,5 @@
|
|||||||
[MASTER]
|
[MASTER]
|
||||||
ignore=external
|
ignore=external,setup.py
|
||||||
reports=no
|
reports=no
|
||||||
|
|
||||||
# Reasons disabled:
|
# Reasons disabled:
|
||||||
|
118
requirements.txt
118
requirements.txt
@ -1,114 +1,4 @@
|
|||||||
# Required for Home Assistant core
|
requests>=2,<3
|
||||||
requests>=2.0
|
pyyaml>=3.11,<4
|
||||||
pyyaml>=3.11
|
pytz>=2015.4
|
||||||
pytz>=2015.2
|
pip>=7.0.0
|
||||||
|
|
||||||
# Optional, needed for specific components
|
|
||||||
|
|
||||||
# Sun (sun)
|
|
||||||
astral>=0.8.1
|
|
||||||
|
|
||||||
# Philips Hue library (lights.hue)
|
|
||||||
phue>=0.8
|
|
||||||
|
|
||||||
# Limitlessled/Easybulb/Milight library (lights.limitlessled)
|
|
||||||
ledcontroller>=1.0.7
|
|
||||||
|
|
||||||
# Chromecast bindings (media_player.cast)
|
|
||||||
pychromecast>=0.6.9
|
|
||||||
|
|
||||||
# Keyboard (keyboard)
|
|
||||||
pyuserinput>=0.1.9
|
|
||||||
|
|
||||||
# Tellstick bindings (*.tellstick)
|
|
||||||
tellcore-py>=1.0.4
|
|
||||||
|
|
||||||
# Nmap bindings (device_tracker.nmap)
|
|
||||||
python-libnmap>=0.6.2
|
|
||||||
|
|
||||||
# PushBullet bindings (notify.pushbullet)
|
|
||||||
pushbullet.py>=0.7.1
|
|
||||||
|
|
||||||
# Nest Thermostat bindings (thermostat.nest)
|
|
||||||
python-nest>=2.3.1
|
|
||||||
|
|
||||||
# Z-Wave (*.zwave)
|
|
||||||
pydispatcher>=2.0.5
|
|
||||||
|
|
||||||
# ISY994 bindings (*.isy994)
|
|
||||||
PyISY>=1.0.5
|
|
||||||
|
|
||||||
# PSutil (sensor.systemmonitor)
|
|
||||||
psutil>=3.0.0
|
|
||||||
|
|
||||||
# Pushover bindings (notify.pushover)
|
|
||||||
python-pushover>=0.2
|
|
||||||
|
|
||||||
# Transmission Torrent Client (*.transmission)
|
|
||||||
transmissionrpc>=0.11
|
|
||||||
|
|
||||||
# OpenWeatherMap Web API (sensor.openweathermap)
|
|
||||||
pyowm>=2.2.1
|
|
||||||
|
|
||||||
# XMPP Bindings (notify.xmpp)
|
|
||||||
sleekxmpp>=1.3.1
|
|
||||||
|
|
||||||
# Blockchain (sensor.bitcoin)
|
|
||||||
blockchain>=1.1.2
|
|
||||||
|
|
||||||
# MPD Bindings (media_player.mpd)
|
|
||||||
python-mpd2>=0.5.4
|
|
||||||
|
|
||||||
# Hikvision (switch.hikvisioncam)
|
|
||||||
hikvision>=0.4
|
|
||||||
|
|
||||||
# console log coloring
|
|
||||||
colorlog>=2.6.0
|
|
||||||
|
|
||||||
# JSON-RPC interface (media_player.kodi)
|
|
||||||
jsonrpc-requests>=0.1
|
|
||||||
|
|
||||||
# Forecast.io Bindings (sensor.forecast)
|
|
||||||
python-forecastio>=1.3.3
|
|
||||||
|
|
||||||
# Firmata Bindings (*.arduino)
|
|
||||||
PyMata==2.07a
|
|
||||||
|
|
||||||
#Rfxtrx sensor
|
|
||||||
https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip
|
|
||||||
|
|
||||||
# Mysensors
|
|
||||||
https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1
|
|
||||||
|
|
||||||
# Netgear (device_tracker.netgear)
|
|
||||||
pynetgear>=0.1
|
|
||||||
|
|
||||||
# Netdisco (discovery)
|
|
||||||
netdisco>=0.1
|
|
||||||
|
|
||||||
# Wemo (switch.wemo)
|
|
||||||
pywemo>=0.1
|
|
||||||
|
|
||||||
# Wink (*.wink)
|
|
||||||
https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1
|
|
||||||
|
|
||||||
# Slack notifier
|
|
||||||
slacker>=0.6.8
|
|
||||||
|
|
||||||
# Temper sensors
|
|
||||||
https://github.com/rkabadi/temper-python/archive/master.zip
|
|
||||||
|
|
||||||
# PyEdimax
|
|
||||||
https://github.com/rkabadi/pyedimax/archive/master.zip
|
|
||||||
|
|
||||||
# RPI-GPIO platform
|
|
||||||
RPi.GPIO >=0.5.11
|
|
||||||
|
|
||||||
# PAHO MQTT Binding (protocol.mqtt)
|
|
||||||
paho-mqtt>=1.1
|
|
||||||
|
|
||||||
# PyModbus (modbus)
|
|
||||||
https://github.com/bashwork/pymodbus/archive/python3.zip#pymodbus>=1.2.0
|
|
||||||
|
|
||||||
# Verisure
|
|
||||||
https://github.com/persandstrom/python-verisure/archive/master.zip
|
|
||||||
|
121
requirements_all.txt
Normal file
121
requirements_all.txt
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# Required for Home Assistant core
|
||||||
|
requests>=2,<3
|
||||||
|
pyyaml>=3.11,<4
|
||||||
|
pytz>=2015.4
|
||||||
|
pip>=7.0.0
|
||||||
|
|
||||||
|
# Optional, needed for specific components
|
||||||
|
|
||||||
|
# Sun (sun)
|
||||||
|
astral==0.8.1
|
||||||
|
|
||||||
|
# Philips Hue library (lights.hue)
|
||||||
|
phue==0.8
|
||||||
|
|
||||||
|
# Limitlessled/Easybulb/Milight library (lights.limitlessled)
|
||||||
|
ledcontroller==1.0.7
|
||||||
|
|
||||||
|
# Chromecast bindings (media_player.cast)
|
||||||
|
pychromecast==0.6.10
|
||||||
|
|
||||||
|
# Keyboard (keyboard)
|
||||||
|
pyuserinput==0.1.9
|
||||||
|
|
||||||
|
# Tellstick bindings (*.tellstick)
|
||||||
|
tellcore-py==1.0.4
|
||||||
|
|
||||||
|
# Nmap bindings (device_tracker.nmap)
|
||||||
|
python-libnmap==0.6.3
|
||||||
|
|
||||||
|
# PushBullet bindings (notify.pushbullet)
|
||||||
|
pushbullet.py==0.7.1
|
||||||
|
|
||||||
|
# Nest Thermostat bindings (thermostat.nest)
|
||||||
|
python-nest==2.4.0
|
||||||
|
|
||||||
|
# Z-Wave (*.zwave)
|
||||||
|
pydispatcher==2.0.5
|
||||||
|
|
||||||
|
# ISY994 bindings (*.isy994)
|
||||||
|
PyISY==1.0.5
|
||||||
|
|
||||||
|
# PSutil (sensor.systemmonitor)
|
||||||
|
psutil==3.0.0
|
||||||
|
|
||||||
|
# Pushover bindings (notify.pushover)
|
||||||
|
python-pushover==0.2
|
||||||
|
|
||||||
|
# Transmission Torrent Client (*.transmission)
|
||||||
|
transmissionrpc==0.11
|
||||||
|
|
||||||
|
# OpenWeatherMap Web API (sensor.openweathermap)
|
||||||
|
pyowm==2.2.1
|
||||||
|
|
||||||
|
# XMPP Bindings (notify.xmpp)
|
||||||
|
sleekxmpp==1.3.1
|
||||||
|
dnspython3==1.12.0
|
||||||
|
|
||||||
|
# Blockchain (sensor.bitcoin)
|
||||||
|
blockchain==1.1.2
|
||||||
|
|
||||||
|
# MPD Bindings (media_player.mpd)
|
||||||
|
python-mpd2==0.5.4
|
||||||
|
|
||||||
|
# Hikvision (switch.hikvisioncam)
|
||||||
|
hikvision==0.4
|
||||||
|
|
||||||
|
# console log coloring
|
||||||
|
colorlog==2.6.0
|
||||||
|
|
||||||
|
# JSON-RPC interface (media_player.kodi)
|
||||||
|
jsonrpc-requests==0.1
|
||||||
|
|
||||||
|
# Forecast.io Bindings (sensor.forecast)
|
||||||
|
python-forecastio==1.3.3
|
||||||
|
|
||||||
|
# Firmata Bindings (*.arduino)
|
||||||
|
PyMata==2.07a
|
||||||
|
|
||||||
|
# Rfxtrx sensor (sensor.rfxtrx)
|
||||||
|
https://github.com/Danielhiversen/pyRFXtrx/archive/ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip
|
||||||
|
|
||||||
|
# Mysensors
|
||||||
|
https://github.com/theolind/pymysensors/archive/35b87d880147a34107da0d40cb815d75e6cb4af7.zip
|
||||||
|
|
||||||
|
# Netgear (device_tracker.netgear)
|
||||||
|
pynetgear==0.3
|
||||||
|
|
||||||
|
# Netdisco (discovery)
|
||||||
|
netdisco==0.3
|
||||||
|
|
||||||
|
# Wemo (switch.wemo)
|
||||||
|
pywemo==0.2
|
||||||
|
|
||||||
|
# Wink (*.wink)
|
||||||
|
https://github.com/balloob/python-wink/archive/c2b700e8ca866159566ecf5e644d9c297f69f257.zip
|
||||||
|
|
||||||
|
# Slack notifier (notify.slack)
|
||||||
|
slacker==0.6.8
|
||||||
|
|
||||||
|
# Temper sensors (sensor.temper)
|
||||||
|
https://github.com/rkabadi/temper-python/archive/3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip
|
||||||
|
|
||||||
|
# PyEdimax
|
||||||
|
https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip
|
||||||
|
|
||||||
|
# RPI-GPIO platform (*.rpi_gpio)
|
||||||
|
# Uncomment for Raspberry Pi
|
||||||
|
# RPi.GPIO ==0.5.11
|
||||||
|
|
||||||
|
# Adafruit temperature/humidity sensor
|
||||||
|
# uncomment on a Raspberry Pi / Beaglebone
|
||||||
|
# http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip
|
||||||
|
|
||||||
|
# PAHO MQTT Binding (mqtt)
|
||||||
|
paho-mqtt==1.1
|
||||||
|
|
||||||
|
# PyModbus (modbus)
|
||||||
|
https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip
|
||||||
|
|
||||||
|
# Verisure (verisure)
|
||||||
|
https://github.com/persandstrom/python-verisure/archive/9873c4527f01b1ba1f72ae60f7f35854390d59be.zip
|
@ -79,7 +79,7 @@ case "$1" in
|
|||||||
uninstall)
|
uninstall)
|
||||||
uninstall
|
uninstall
|
||||||
;;
|
;;
|
||||||
retart)
|
restart)
|
||||||
stop
|
stop
|
||||||
start
|
start
|
||||||
;;
|
;;
|
||||||
|
@ -3,6 +3,4 @@ if [ ${PWD##*/} == "scripts" ]; then
|
|||||||
cd ..
|
cd ..
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git pull --recurse-submodules=yes
|
git pull
|
||||||
git submodule update --init --recursive
|
|
||||||
python3 -m pip install -r requirements.txt
|
|
||||||
|
53
setup.py
Executable file
53
setup.py
Executable file
@ -0,0 +1,53 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
PACKAGE_NAME = 'homeassistant'
|
||||||
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
with open(os.path.join(HERE, PACKAGE_NAME, 'const.py')) as fp:
|
||||||
|
VERSION = re.search("__version__ = ['\"]([^']+)['\"]\n", fp.read()).group(1)
|
||||||
|
DOWNLOAD_URL = \
|
||||||
|
'https://github.com/balloob/home-assistant/tarball/{}'.format(VERSION)
|
||||||
|
|
||||||
|
PACKAGES = find_packages() + \
|
||||||
|
['homeassistant.external', 'homeassistant.external.noop',
|
||||||
|
'homeassistant.external.nzbclients', 'homeassistant.external.vera']
|
||||||
|
|
||||||
|
PACKAGE_DATA = \
|
||||||
|
{'homeassistant.components.frontend': ['index.html.template'],
|
||||||
|
'homeassistant.components.frontend.www_static': ['*.*'],
|
||||||
|
'homeassistant.components.frontend.www_static.images': ['*.*']}
|
||||||
|
|
||||||
|
REQUIRES = \
|
||||||
|
[line.strip() for line in open('requirements.txt', 'r')]
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=PACKAGE_NAME,
|
||||||
|
version=VERSION,
|
||||||
|
license='MIT License',
|
||||||
|
url='https://home-assistant.io/',
|
||||||
|
download_url=DOWNLOAD_URL,
|
||||||
|
author='Paulus Schoutsen',
|
||||||
|
author_email='paulus@paulusschoutsen.nl',
|
||||||
|
description='Open-source home automation platform running on Python 3.',
|
||||||
|
packages=PACKAGES,
|
||||||
|
include_package_data=True,
|
||||||
|
package_data=PACKAGE_DATA,
|
||||||
|
zip_safe=False,
|
||||||
|
platforms='any',
|
||||||
|
install_requires=REQUIRES,
|
||||||
|
keywords=['home', 'automation'],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'hass = homeassistant.__main__:main'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
classifiers=[
|
||||||
|
'Intended Audience :: End Users/Desktop',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python :: 3.4',
|
||||||
|
'Topic :: Home Automation'
|
||||||
|
]
|
||||||
|
)
|
@ -78,3 +78,18 @@ class TestAutomationEvent(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
self.assertEqual(['hello.world'], self.calls[0].data[ATTR_ENTITY_ID])
|
self.assertEqual(['hello.world'], self.calls[0].data[ATTR_ENTITY_ID])
|
||||||
|
|
||||||
|
def test_service_specify_entity_id_list(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
CONF_PLATFORM: 'event',
|
||||||
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
|
automation.CONF_SERVICE_ENTITY_ID: ['hello.world', 'hello.world2']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
self.assertEqual(['hello.world', 'hello.world2'], self.calls[0].data[ATTR_ENTITY_ID])
|
||||||
|
@ -16,6 +16,8 @@ from datetime import datetime
|
|||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
|
from homeassistant.exceptions import (
|
||||||
|
HomeAssistantError, InvalidEntityFormatError)
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -41,7 +43,7 @@ class TestHomeAssistant(unittest.TestCase):
|
|||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
try:
|
try:
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
except ha.HomeAssistantError:
|
except HomeAssistantError:
|
||||||
# Already stopped after the block till stopped test
|
# Already stopped after the block till stopped test
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -250,7 +252,7 @@ class TestState(unittest.TestCase):
|
|||||||
def test_init(self):
|
def test_init(self):
|
||||||
""" Test state.init """
|
""" Test state.init """
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
ha.InvalidEntityFormatError, ha.State,
|
InvalidEntityFormatError, ha.State,
|
||||||
'invalid_entity_format', 'test_state')
|
'invalid_entity_format', 'test_state')
|
||||||
|
|
||||||
def test_domain(self):
|
def test_domain(self):
|
||||||
@ -489,18 +491,24 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
def test_config_dir_set_correct(self):
|
def test_config_dir_set_correct(self):
|
||||||
""" Test config dir set correct. """
|
""" Test config dir set correct. """
|
||||||
self.assertEqual(os.path.join(os.getcwd(), "config"),
|
data_dir = os.getenv('APPDATA') if os.name == "nt" \
|
||||||
|
else os.path.expanduser('~')
|
||||||
|
self.assertEqual(os.path.join(data_dir, ".homeassistant"),
|
||||||
self.config.config_dir)
|
self.config.config_dir)
|
||||||
|
|
||||||
def test_path_with_file(self):
|
def test_path_with_file(self):
|
||||||
""" Test get_config_path method. """
|
""" Test get_config_path method. """
|
||||||
self.assertEqual(os.path.join(os.getcwd(), "config", "test.conf"),
|
data_dir = os.getenv('APPDATA') if os.name == "nt" \
|
||||||
|
else os.path.expanduser('~')
|
||||||
|
self.assertEqual(os.path.join(data_dir, ".homeassistant", "test.conf"),
|
||||||
self.config.path("test.conf"))
|
self.config.path("test.conf"))
|
||||||
|
|
||||||
def test_path_with_dir_and_file(self):
|
def test_path_with_dir_and_file(self):
|
||||||
""" Test get_config_path method. """
|
""" Test get_config_path method. """
|
||||||
|
data_dir = os.getenv('APPDATA') if os.name == "nt" \
|
||||||
|
else os.path.expanduser('~')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
os.path.join(os.getcwd(), "config", "dir", "test.conf"),
|
os.path.join(data_dir, ".homeassistant", "dir", "test.conf"),
|
||||||
self.config.path("dir", "test.conf"))
|
self.config.path("dir", "test.conf"))
|
||||||
|
|
||||||
def test_temperature_not_convert_if_no_preference(self):
|
def test_temperature_not_convert_if_no_preference(self):
|
||||||
|
@ -130,9 +130,12 @@ class TestRemoteMethods(unittest.TestCase):
|
|||||||
|
|
||||||
def test_set_state(self):
|
def test_set_state(self):
|
||||||
""" Test Python API set_state. """
|
""" Test Python API set_state. """
|
||||||
self.assertTrue(remote.set_state(master_api, 'test.test', 'set_test'))
|
hass.states.set('test.test', 'set_test')
|
||||||
|
|
||||||
self.assertEqual('set_test', hass.states.get('test.test').state)
|
state = hass.states.get('test.test')
|
||||||
|
|
||||||
|
self.assertIsNotNone(state)
|
||||||
|
self.assertEqual('set_test', state.state)
|
||||||
|
|
||||||
self.assertFalse(remote.set_state(broken_api, 'test.test', 'set_test'))
|
self.assertFalse(remote.set_state(broken_api, 'test.test', 'set_test'))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user