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