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:
Paulus Schoutsen 2015-08-29 23:40:38 -07:00
commit 74e4b024c0
85 changed files with 2159 additions and 1008 deletions

View File

@ -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

View File

@ -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

View File

@ -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/* && \

View File

@ -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']
IS_VIRTUAL = (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or
hasattr(sys, 'real_prefix'))
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 from homeassistant import bootstrap
import homeassistant.config as config_util
return bootstrap from homeassistant.const import EVENT_HOMEASSISTANT_START
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:

View File

@ -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):

View File

@ -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)

View File

@ -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",
{ {

View File

@ -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. """

View 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

View File

@ -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")

View File

@ -26,8 +26,12 @@ from collections import namedtuple
import subprocess import subprocess
import re import re
try:
from libnmap.process import NmapProcess from libnmap.process import NmapProcess
from libnmap.parser import NmapParser, NmapParserException 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

View 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

View File

@ -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

View File

@ -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>

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,2 +0,0 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = ""

View 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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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. """

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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'

View File

@ -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

View File

@ -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):

View File

@ -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__)

View File

@ -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__)

View File

@ -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):

View File

@ -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)

View File

@ -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'],

View File

@ -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),
]) ])

View 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

View File

@ -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

View 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

View File

@ -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):

View File

@ -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', ''],

View File

@ -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),

View 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

View File

@ -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'],

View File

@ -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

View File

@ -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

View File

@ -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'],

View File

@ -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):

View File

@ -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"

View File

@ -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__)

View File

@ -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

View 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()

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)])

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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__)

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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 = '*'

View File

@ -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).

View 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

View File

@ -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,

View File

@ -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

View File

@ -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")

View File

@ -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:

View File

@ -1,5 +1,5 @@
[MASTER] [MASTER]
ignore=external ignore=external,setup.py
reports=no reports=no
# Reasons disabled: # Reasons disabled:

View File

@ -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
View 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

View File

@ -79,7 +79,7 @@ case "$1" in
uninstall) uninstall)
uninstall uninstall
;; ;;
retart) restart)
stop stop
start start
;; ;;

View File

@ -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
View 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'
]
)

View File

@ -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])

View File

@ -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):

View File

@ -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'))