Merge pull request #299 from balloob/dev

0.7-final
This commit is contained in:
Paulus Schoutsen 2015-09-01 01:02:32 -07:00
commit 3bbdc5bcd7
54 changed files with 750 additions and 372 deletions

View File

@ -31,6 +31,7 @@ omit =
homeassistant/components/browser.py
homeassistant/components/camera/*
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py

2
.gitignore vendored
View File

@ -9,6 +9,8 @@ config/custom_components/*
!config/custom_components/hello_world.py
!config/custom_components/mqtt_example.py
tests/config/home-assistant.log
# Hide sublime text stuff
*.sublime-project
*.sublime-workspace

View File

@ -5,6 +5,12 @@ VOLUME /config
RUN pip3 install --no-cache-dir -r requirements_all.txt
# For the nmap tracker
RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Open Z-Wave disabled because broken
#RUN apt-get update && \
# apt-get install -y cython3 libudev-dev && \
# apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \

View File

@ -1,15 +1,23 @@
# Home Assistant [![Build Status](https://travis-ci.org/balloob/home-assistant.svg?branch=master)](https://travis-ci.org/balloob/home-assistant) [![Coverage Status](https://img.shields.io/coveralls/balloob/home-assistant.svg)](https://coveralls.io/r/balloob/home-assistant?branch=master) [![Join the chat at https://gitter.im/balloob/home-assistant](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control. [Open a demo.](https://home-assistant.io/demo/)
[demo]: https://home-assistant.io/demo/
Check out [the website](https://home-assistant.io) for installation instructions, tutorials and documentation.
Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control.
[![screenshot-states](https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png)](https://home-assistant.io/demo/)
To get started:
```bash
python3 -m pip install homeassistant
hass --open-ui
```
Check out [the website](https://home-assistant.io) for [a demo][demo], installation instructions, tutorials and documentation.
[![screenshot-states](https://raw.github.com/balloob/home-assistant/master/docs/screenshots.png)][demo]
Examples of devices it can interface it:
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Efergy](https://efergy.com) plugs, [Edimax](http://www.edimax.com/) switches, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Edimax](http://www.edimax.com/) switches, [Efergy](https://efergy.com) energy monitoring, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), and [Kodi (XBMC)](http://kodi.tv/)
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
* Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org).
@ -21,26 +29,9 @@ Built home automation on top of your devices:
* Turn on the lights when people get home after sun set
* Turn on lights slowly during sun set to compensate for less light
* Turn off all lights and devices when everybody leaves the house
* Offers a [REST API](https://home-assistant.io/developers/api.html) for easy integration with other projects
* Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), and [Jabber (XMPP)](http://xmpp.org)
The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html).
If you run into issues while using Home Assistant or during development of a component, reach out to the [Home Assistant help section](https://home-assistant.io/help/) how to reach us.
## Quick-start guide
Running Home Assistant requires [Python 3.4](https://www.python.org/). Run the following code to get up and running:
```
git clone --recursive https://github.com/balloob/home-assistant.git
python3 -m venv home-assistant
cd home-assistant
python3 -m homeassistant --open-ui
```
The last command will start the Home Assistant server and launch its web interface. By default Home Assistant looks for the configuration file `config/configuration.yaml`. A standard configuration file will be written if none exists.
If you are still exploring if you want to use Home Assistant in the first place, you can enable the demo mode by adding the `--demo-mode` argument to the last command.
Please see [the getting started guide](https://home-assistant.io/getting-started/) on how to further configure Home Assistant.
If you run into issues while using Home Assistant or during development of a component, check the [Home Assistant help section](https://home-assistant.io/help/) how to reach us.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -7,7 +7,16 @@ import argparse
from homeassistant import bootstrap
import homeassistant.config as config_util
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START
def validate_python():
""" Validate we're running the right Python version. """
major, minor = sys.version_info[:2]
if major < 3 or (major == 3 and minor < 4):
print("Home Assistant requires atleast Python 3.4")
sys.exit(1)
def ensure_config_path(config_dir):
@ -54,6 +63,7 @@ def get_arguments():
""" Get parsed passed in arguments. """
parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.")
parser.add_argument('--version', action='version', version=__version__)
parser.add_argument(
'-c', '--config',
metavar='path_to_config_dir',
@ -67,25 +77,99 @@ def get_arguments():
'--open-ui',
action='store_true',
help='Open the webinterface in a browser')
parser.add_argument(
'-v', '--verbose',
action='store_true',
help="Enable verbose logging to file.")
parser.add_argument(
'--pid-file',
metavar='path_to_pid_file',
default=None,
help='Path to PID file useful for running as daemon')
if os.name != "nt":
parser.add_argument(
'--daemon',
action='store_true',
help='Run Home Assistant as daemon')
return parser.parse_args()
arguments = parser.parse_args()
if os.name == "nt":
arguments.daemon = False
return arguments
def daemonize():
""" Move current process to daemon process """
# create first fork
pid = os.fork()
if pid > 0:
sys.exit(0)
# decouple fork
os.setsid()
os.umask(0)
# create second fork
pid = os.fork()
if pid > 0:
sys.exit(0)
def check_pid(pid_file):
""" Check that HA is not already running """
# check pid file
try:
pid = int(open(pid_file, 'r').readline())
except IOError:
# PID File does not exist
return
try:
os.kill(pid, 0)
except OSError:
# PID does not exist
return
print('Fatal Error: HomeAssistant is already running.')
sys.exit(1)
def write_pid(pid_file):
""" Create PID File """
pid = os.getpid()
try:
open(pid_file, 'w').write(str(pid))
except IOError:
print('Fatal Error: Unable to write pid file {}'.format(pid_file))
sys.exit(1)
def main():
""" Starts Home Assistant. """
validate_python()
args = get_arguments()
config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir)
# daemon functions
if args.pid_file:
check_pid(args.pid_file)
if args.daemon:
daemonize()
if args.pid_file:
write_pid(args.pid_file)
if args.demo_mode:
hass = bootstrap.from_config_dict({
'frontend': {},
'demo': {}
}, config_dir=config_dir)
}, config_dir=config_dir, daemon=args.daemon, verbose=args.verbose)
else:
config_file = ensure_config_file(config_dir)
hass = bootstrap.from_config_file(config_file)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose)
if args.open_ui:
def open_browser(event):

View File

@ -53,9 +53,6 @@ def setup_component(hass, domain, config=None):
return False
for component in components:
if component in hass.config.components:
continue
if not _setup_component(hass, component, config):
return False
@ -78,6 +75,8 @@ def _handle_requirements(hass, component, name):
def _setup_component(hass, domain, config):
""" Setup a component for Home Assistant. """
if domain in hass.config.components:
return True
component = loader.get_component(domain)
missing_deps = [dep for dep in component.DEPENDENCIES
@ -150,8 +149,9 @@ def mount_local_lib_path(config_dir):
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, config_dir=None, enable_log=True):
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False):
"""
Tries to configure Home Assistant from a config dict.
@ -167,7 +167,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
if enable_log:
enable_logging(hass)
enable_logging(hass, verbose, daemon)
_ensure_loader_prepared(hass)
@ -196,7 +196,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
return hass
def from_config_file(config_path, hass=None):
def from_config_file(config_path, hass=None, verbose=False, daemon=False):
"""
Reads the configuration file and tries to start all the required
functionality. Will add functionality to 'hass' parameter if given,
@ -210,35 +210,36 @@ def from_config_file(config_path, hass=None):
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
enable_logging(hass)
enable_logging(hass, verbose, daemon)
config_dict = config_util.load_config_file(config_path)
return from_config_dict(config_dict, hass, enable_log=False)
def enable_logging(hass):
def enable_logging(hass, verbose=False, daemon=False):
""" Setup the logging for home assistant. """
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s")
try:
from colorlog import ColoredFormatter
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
fmt,
datefmt='%y-%m-%d %H:%M:%S',
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
))
except ImportError:
_LOGGER.warning(
"Colorlog package not found, console coloring disabled")
if not daemon:
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s")
try:
from colorlog import ColoredFormatter
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
fmt,
datefmt='%y-%m-%d %H:%M:%S',
reset=True,
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
))
except ImportError:
_LOGGER.warning(
"Colorlog package not found, console coloring disabled")
# Log errors to a file if we have write access to file or config dir
err_log_path = hass.config.path('home-assistant.log')
@ -252,11 +253,13 @@ def enable_logging(hass):
err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True)
err_handler.setLevel(logging.WARNING)
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(
logging.Formatter('%(asctime)s %(name)s: %(message)s',
datefmt='%y-%m-%d %H:%M:%S'))
logging.getLogger('').addHandler(err_handler)
logger = logging.getLogger('')
logger.addHandler(err_handler)
logger.setLevel(logging.INFO) # this sets the minimum log level
else:
_LOGGER.error(

View File

@ -10,7 +10,7 @@ import re
from homeassistant import core
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
DOMAIN = "conversation"
DEPENDENCIES = []
@ -44,7 +44,7 @@ def setup(hass, config):
entity_ids = [
state.entity_id for state in hass.states.all()
if state.attributes.get(ATTR_FRIENDLY_NAME, "").lower() == name]
if state.name.lower() == name]
if not entity_ids:
logger.error(

View File

@ -46,12 +46,12 @@ def setup(hass, config):
hass, component, {component: {CONF_PLATFORM: 'demo'}})
# Setup room groups
lights = hass.states.entity_ids('light')
switches = hass.states.entity_ids('switch')
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
media_players = sorted(hass.states.entity_ids('media_player'))
group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0],
group.setup_group(hass, 'living room', [lights[2], lights[1], switches[0],
media_players[1]])
group.setup_group(hass, 'bedroom', [lights[2], switches[1],
group.setup_group(hass, 'bedroom', [lights[0], switches[1],
media_players[0]])
# Setup IP Camera
@ -68,7 +68,7 @@ def setup(hass, config):
hass, 'script',
{'script': {
'demo': {
'alias': 'Demo {}'.format(lights[0]),
'alias': 'Toggle {}'.format(lights[0].split('.')[1]),
'sequence': [{
'execute_service': 'light.turn_off',
'service_data': {ATTR_ENTITY_ID: lights[0]}

View File

@ -10,11 +10,11 @@ import os
import csv
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
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import (
@ -63,8 +63,8 @@ def setup(hass, config):
tracker_type = config[DOMAIN].get(CONF_PLATFORM)
tracker_implementation = get_component(
'device_tracker.{}'.format(tracker_type))
tracker_implementation = \
prepare_setup_platform(hass, config, DOMAIN, tracker_type)
if tracker_implementation is None:
_LOGGER.error("Unknown device_tracker type specified: %s.",

View File

@ -0,0 +1,154 @@
"""
homeassistant.components.device_tracker.aruba
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Aruba Access Point for device
presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the Aruba tracker you will need to add something like the following
to your config/configuration.yaml. You also need to enable Telnet in the
configuration pages.
device_tracker:
platform: aruba
host: YOUR_ACCESS_POINT_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<name>([^\s]+))\s+' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s+' +
r'(?P<os>([^\s]+))\s+' +
r'(?P<network>([^\s]+))\s+' +
r'(?P<ap>([^\s]+))\s+' +
r'(?P<channel>([^\s]+))\s+' +
r'(?P<type>([^\s]+))\s+' +
r'(?P<role>([^\s]+))\s+' +
r'(?P<signal>([^\s]+))\s+' +
r'(?P<speed>([^\s]+))')
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a Aruba scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = ArubaDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class ArubaDeviceScanner(object):
""" This class queries a Aruba Acces Point for connected devices. """
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_aruba_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['name']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Aruba Access Point is up to date.
Returns boolean if scanning successful. """
if not self.success_init:
return False
with self.lock:
data = self.get_aruba_data()
if not data:
return False
self.last_results = data.values()
return True
def get_aruba_data(self):
""" Retrieve data from Aruba Access Point and return parsed
result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'User: ')
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(('show clients\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(),
'name': match.group('name')
}
return devices

View File

@ -19,6 +19,11 @@ hosts
*Required
The IP addresses to scan in the network-prefix notation (192.168.1.1/24) or
the range notation (192.168.1.1-255).
home_interval
*Optional
Number of minutes it will not scan devices that it found in previous results.
This is to save battery.
"""
import logging
from datetime import timedelta
@ -26,13 +31,6 @@ 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
from homeassistant.helpers import validate_config
@ -47,7 +45,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.1']
REQUIREMENTS = ['python-nmap==0.4.1']
def get_scanner(hass, config):
@ -56,10 +54,6 @@ 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
@ -76,7 +70,7 @@ def _arp(ip_address):
if match:
return match.group(0)
_LOGGER.info("No MAC address found for %s", ip_address)
return ''
return None
class NmapDeviceScanner(object):
@ -89,8 +83,7 @@ class NmapDeviceScanner(object):
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
self.home_interval = timedelta(minutes=minutes)
self.success_init = True
self._update_info()
self.success_init = self._update_info()
_LOGGER.info("nmap scanner initialized")
def scan_devices(self):
@ -112,43 +105,16 @@ class NmapDeviceScanner(object):
else:
return None
def _parse_results(self, stdout):
""" Parses results from an nmap scan.
Returns True if successful, False otherwise. """
try:
results = NmapParser.parse(stdout)
now = dt_util.now()
self.last_results = []
for host in results.hosts:
if host.is_up():
if host.hostnames:
name = host.hostnames[0]
else:
name = host.ipv4
if host.mac:
mac = host.mac
else:
mac = _arp(host.ipv4)
if mac:
device = Device(mac.upper(), name, host.ipv4, now)
self.last_results.append(device)
_LOGGER.info("nmap scan successful")
return True
except NmapParserException as parse_exc:
_LOGGER.error("failed to parse nmap results: %s", parse_exc.msg)
self.last_results = []
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Scans the network for devices.
Returns boolean if scanning successful. """
if not self.success_init:
return False
_LOGGER.info("Scanning")
options = "-F --host-timeout 5"
from nmap import PortScanner, PortScannerError
scanner = PortScanner()
options = "-sP --host-timeout 5"
exclude_targets = set()
if self.home_interval:
now = dt_util.now()
@ -159,14 +125,24 @@ class NmapDeviceScanner(object):
target_list = [t.ip for t in exclude_targets]
options += " --exclude {}".format(",".join(target_list))
nmap = NmapProcess(targets=self.hosts, options=options)
nmap.run()
if nmap.rc == 0:
if self._parse_results(nmap.stdout):
self.last_results.extend(exclude_targets)
else:
self.last_results = []
_LOGGER.error(nmap.stderr)
try:
result = scanner.scan(hosts=self.hosts, arguments=options)
except PortScannerError:
return False
now = dt_util.now()
self.last_results = []
for ipv4, info in result['scan'].items():
if info['status']['state'] != 'up':
continue
name = info['hostnames'][0] if info['hostnames'] else ipv4
# Mac address only returned if nmap ran as root
mac = info['addresses'].get('mac') or _arp(ipv4)
if mac is None:
continue
device = Device(mac.upper(), name, ipv4, now)
self.last_results.append(device)
self.last_results.extend(exclude_targets)
_LOGGER.info("nmap scan successful")
return True

View File

@ -33,15 +33,16 @@
right: 0;
bottom: 0;
}
#init p {
margin-bottom: 101px;
#init div {
line-height: 34px;
margin-bottom: 89px;
}
</style>
</head>
<body fullbleed>
<div id='init'>
<img src='/static/splash.png' height='230' />
<p>Initializing</p>
<div>Initializing</div>
</div>
<script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />

View File

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

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit a97750b5dd887af42030e01bfe50bc3c60183514
Subproject commit b0b12e20e0f61df849c414c2dfbcf9923f784631

View File

@ -104,7 +104,7 @@ def setup(hass, config):
""" Sets up all groups found definded in the configuration. """
for name, entity_ids in config.get(DOMAIN, {}).items():
if isinstance(entity_ids, str):
entity_ids = entity_ids.split(",")
entity_ids = [ent.strip() for ent in entity_ids.split(",")]
setup_group(hass, name, entity_ids)
return True

View File

@ -208,6 +208,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
""" Registers a path wit the server. """
self.paths.append((method, url, callback, require_auth))
def log_message(self, fmt, *args):
""" Redirect built-in log to HA logging """
# pylint: disable=no-self-use
_LOGGER.info(fmt, *args)
# pylint: disable=too-many-public-methods,too-many-locals
class RequestHandler(SimpleHTTPRequestHandler):
@ -225,6 +230,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
self._session = None
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """
_LOGGER.info(fmt, *arguments)
def _handle_request(self, method): # pylint: disable=too-many-branches
""" Does some common checks and calls appropriate method. """
url = urlparse(self.path)

View File

@ -29,8 +29,11 @@ def setup(hass, config=None):
- Available components:
https://home-assistant.io/components/
- Chat room:
https://gitter.im/balloob/home-assistant
- Troubleshooting your configuration:
https://home-assistant.io/getting-started/troubleshooting-configuration.html
- Getting help:
https://home-assistant.io/help/
This message is generated by the introduction component. You can
disable it in configuration.yaml.

View File

@ -12,9 +12,8 @@ from homeassistant.components.light import (
LIGHT_COLORS = [
[0.861, 0.3259],
[0.6389, 0.3028],
[0.1684, 0.0416]
[0.368, 0.180],
[0.460, 0.470],
]
@ -22,8 +21,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo lights. """
add_devices_callback([
DemoLight("Bed Light", False),
DemoLight("Ceiling", True),
DemoLight("Kitchen", True)
DemoLight("Ceiling Lights", True, LIGHT_COLORS[0]),
DemoLight("Kitchen Lights", True, LIGHT_COLORS[1])
])

View File

@ -19,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.10']
REQUIREMENTS = ['pychromecast==0.6.12']
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 | \

View File

@ -14,6 +14,7 @@ media_player:
server: 127.0.0.1
port: 6600
location: bedroom
password: superSecretPassword123
Variables:
@ -28,6 +29,10 @@ Port of the Music Player Daemon, defaults to 6600. Example: 6600
location
*Optional
Location of your Music Player Daemon.
password
*Optional
Password for your Music Player Daemon.
"""
import logging
import socket
@ -61,6 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
daemon = config.get('server', None)
port = config.get('port', 6600)
location = config.get('location', 'MPD')
password = config.get('password', None)
global mpd # pylint: disable=invalid-name
if mpd is None:
@ -71,6 +77,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
mpd_client = mpd.MPDClient()
mpd_client.connect(daemon, port)
if password is not None:
mpd_client.password(password)
mpd_client.close()
mpd_client.disconnect()
except socket.error:
@ -79,8 +89,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"Please check your settings")
return False
except mpd.CommandError as error:
add_devices([MpdDevice(daemon, port, location)])
if "incorrect password" in str(error):
_LOGGER.error(
"MPD reported incorrect password. "
"Please check your password.")
return False
else:
raise
add_devices([MpdDevice(daemon, port, location, password)])
class MpdDevice(MediaPlayerDevice):
@ -89,10 +109,11 @@ class MpdDevice(MediaPlayerDevice):
# MPD confuses pylint
# pylint: disable=no-member, abstract-method
def __init__(self, server, port, location):
def __init__(self, server, port, location, password):
self.server = server
self.port = port
self._name = location
self.password = password
self.status = None
self.currentsong = None
@ -107,6 +128,10 @@ class MpdDevice(MediaPlayerDevice):
self.currentsong = self.client.currentsong()
except mpd.ConnectionError:
self.client.connect(self.server, self.port)
if self.password is not None:
self.client.password(self.password)
self.status = self.client.status()
self.currentsong = self.client.currentsong()
@ -189,11 +214,11 @@ class MpdDevice(MediaPlayerDevice):
def media_play(self):
""" Service to send the MPD the command for play/pause. """
self.client.start()
self.client.pause(0)
def media_pause(self):
""" Service to send the MPD the command for play/pause. """
self.client.pause()
self.client.pause(1)
def media_next_track(self):
""" Service to send the MPD the command for next track. """

View File

@ -24,6 +24,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_TODAY_MWH = "today_mwh"
ATTR_CURRENT_POWER_MWH = "current_power_mwh"
ATTR_SENSOR_STATE = "sensor_state"
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
@ -38,6 +39,7 @@ DISCOVERY_PLATFORMS = {
PROP_TO_ATTR = {
'current_power_mwh': ATTR_CURRENT_POWER_MWH,
'today_power_mw': ATTR_TODAY_MWH,
'sensor_state': ATTR_SENSOR_STATE
}
_LOGGER = logging.getLogger(__name__)
@ -101,6 +103,16 @@ class SwitchDevice(ToggleEntity):
""" Today total power usage in mw. """
return None
@property
def is_standby(self):
""" Is the device in standby. """
return None
@property
def sensor_state(self):
""" Is the sensor on or off. """
return None
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """

View File

@ -13,7 +13,7 @@ from homeassistant.const import DEVICE_DEFAULT_NAME
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo switches. """
add_devices_callback([
DemoSwitch('Ceiling', True),
DemoSwitch('Decorative Lights', True),
DemoSwitch('AC', False)
])

View File

@ -7,8 +7,9 @@ Support for WeMo switches.
import logging
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_ON, STATE_OFF, STATE_STANDBY
REQUIREMENTS = ['pywemo==0.2']
REQUIREMENTS = ['pywemo==0.3']
# pylint: disable=unused-argument
@ -39,6 +40,7 @@ class WemoSwitch(SwitchDevice):
def __init__(self, wemo):
self.wemo = wemo
self.insight_params = None
self.maker_params = None
@property
def unique_id(self):
@ -50,6 +52,16 @@ class WemoSwitch(SwitchDevice):
""" Returns the name of the switch if any. """
return self.wemo.name
@property
def state(self):
""" Returns the state. """
is_on = self.is_on
if not is_on:
return STATE_OFF
elif self.is_standby:
return STATE_STANDBY
return STATE_ON
@property
def current_power_mwh(self):
""" Current power usage in mwh. """
@ -62,6 +74,40 @@ class WemoSwitch(SwitchDevice):
if self.insight_params:
return self.insight_params['todaymw']
@property
def is_standby(self):
""" Is the device on - or in standby. """
if self.insight_params:
standby_state = self.insight_params['state']
# Standby is actually '8' but seems more defensive
# to check for the On and Off states
if standby_state == '1' or standby_state == '0':
return False
else:
return True
@property
def sensor_state(self):
""" Is the sensor on or off. """
if self.maker_params and self.has_sensor:
# Note a state of 1 matches the WeMo app 'not triggered'!
if self.maker_params['sensorstate']:
return STATE_OFF
else:
return STATE_ON
@property
def switch_mode(self):
""" Is the switch configured as toggle(0) or momentary (1). """
if self.maker_params:
return self.maker_params['switchmode']
@property
def has_sensor(self):
""" Is the sensor present? """
if self.maker_params:
return self.maker_params['hassensor']
@property
def is_on(self):
""" True if switch is on. """
@ -78,5 +124,8 @@ class WemoSwitch(SwitchDevice):
def update(self):
""" Update WeMo state. """
self.wemo.get_state(True)
if self.wemo.model.startswith('Belkin Insight'):
if self.wemo.model_name == 'Insight':
self.insight_params = self.wemo.insight_params
self.insight_params['standby_state'] = self.wemo.get_standby_state
elif self.wemo.model_name == 'Maker':
self.maker_params = self.wemo.maker_params

View File

@ -54,7 +54,7 @@ def ensure_config_exists(config_dir, detect_location=True):
config_path = find_config_file(config_dir)
if config_path is None:
print("Unable to find configuration. Creating default one at",
print("Unable to find configuration. Creating default one in",
config_dir)
config_path = create_default_config(config_dir, detect_location)

View File

@ -46,6 +46,7 @@ STATE_CLOSED = 'closed'
STATE_PLAYING = 'playing'
STATE_PAUSED = 'paused'
STATE_IDLE = 'idle'
STATE_STANDBY = 'standby'
# #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event

View File

@ -9,6 +9,7 @@ of entities and react to changes.
import os
import time
import logging
import signal
import threading
import enum
import re
@ -73,13 +74,16 @@ class HomeAssistant(object):
will block until called. """
request_shutdown = threading.Event()
def stop_homeassistant(service):
def stop_homeassistant(*args):
""" Stops Home Assistant. """
request_shutdown.set()
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
if os.name != "nt":
signal.signal(signal.SIGQUIT, stop_homeassistant)
while not request_shutdown.isSet():
try:
time.sleep(1)
@ -761,8 +765,10 @@ def create_timer(hass, interval=TIMER_INTERVAL):
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_timer)
def create_worker_pool(worker_count=MIN_WORKER_THREAD):
def create_worker_pool(worker_count=None):
""" Creates a worker pool to be used. """
if worker_count is None:
worker_count = MIN_WORKER_THREAD
def job_handler(job):
""" Called whenever a job is available to do. """

View File

@ -16,7 +16,7 @@ phue==0.8
ledcontroller==1.0.7
# Chromecast bindings (media_player.cast)
pychromecast==0.6.10
pychromecast==0.6.12
# Keyboard (keyboard)
pyuserinput==0.1.9
@ -25,7 +25,7 @@ pyuserinput==0.1.9
tellcore-py==1.0.4
# Nmap bindings (device_tracker.nmap)
python-libnmap==0.6.3
python-nmap==0.4.1
# PushBullet bindings (notify.pushbullet)
pushbullet.py==0.7.1
@ -89,7 +89,7 @@ pynetgear==0.3
netdisco==0.3
# Wemo (switch.wemo)
pywemo==0.2
pywemo==0.3
# Wink (*.wink)
https://github.com/balloob/python-wink/archive/c2b700e8ca866159566ecf5e644d9c297f69f257.zip

101
scripts/hass-daemon Normal file
View File

@ -0,0 +1,101 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: hass
# Required-Start: $local_fs $network $named $time $syslog
# Required-Stop: $local_fs $network $named $time $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Home\ Assistant
### END INIT INFO
# /etc/init.d Service Script for Home Assistant
# Created with: https://gist.github.com/naholyr/4275302#file-new-service-sh
#
# Installation:
# 1) If any commands need to run before executing hass (like loading a
# virutal environment), put them in PRE_EXEC. This command must end with
# a semicolon.
# 2) Set RUN_AS to the username that should be used to execute hass.
# 3) Copy this script to /etc/init.d/
# sudo cp hass-daemon /etc/init.d/hass-daemon
# sudo chmod +x /etc/init.d/hass-daemon
# 4) Register the daemon with Linux
# sudo update-rc.d hass-daemon defaults
# 5) Install this service
# sudo service hass-daemon install
# 6) Restart Machine
#
# After installation, HA should start automatically. If HA does not start,
# check the log file output for errors.
# /var/opt/homeassistant/home-assistant.log
PRE_EXEC=""
RUN_AS="USER"
PID_FILE="/var/run/hass.pid"
CONFIG_DIR="/var/opt/homeassistant"
FLAGS="-v --config $CONFIG_DIR --pid-file $PID_FILE --daemon"
start() {
if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE); then
echo 'Service already running' >&2
return 1
fi
echo 'Starting service…' >&2
local CMD="$PRE_EXEC hass $FLAGS;"
su -c "$CMD" $RUN_AS
echo 'Service started' >&2
}
stop() {
if [ ! -f "$PID_FILE" ] || ! kill -0 $(cat "$PID_FILE"); then
echo 'Service not running' >&2
return 1
fi
echo 'Stopping service…' >&2
kill -3 $(cat "$PID_FILE")
echo 'Service stopped' >&2
}
install() {
echo "Installing Home Assistant Daemon (hass-daemon)"
echo "999999" > $PID_FILE
chown $RUN_AS $PID_FILE
mkdir -p $CONFIG_DIR
chown $RUN_AS $CONFIG_DIR
}
uninstall() {
echo -n "Are you really sure you want to uninstall this service? That cannot be undone. [yes|No] "
local SURE
read SURE
if [ "$SURE" = "yes" ]; then
stop
rm -fv "$PID_FILE"
echo "Notice: The config directory has not been removed"
echo $CONFIG_DIR
update-rc.d -f hass-daemon remove
rm -fv "$0"
echo "Home Assistant Daemon has been removed. Home Assistant is still installed."
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
install)
install
;;
uninstall)
uninstall
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart|install|uninstall}"
esac

View File

@ -1,78 +0,0 @@
#!/bin/sh
# To script is for running Home Assistant as a service and automatically starting it on boot.
# Assuming you have cloned the HA repo into /home/pi/Apps/home-assistant adjust this path if necessary
# This also assumes you installed HA on your raspberry pi using the instructions here:
# https://home-assistant.io/getting-started/
#
# To install to the following:
# sudo cp /home/pi/Apps/home-assistant/scripts/homeassistant-pi.sh /etc/init.d/homeassistant.sh
# sudo chmod +x /etc/init.d/homeassistant.sh
# sudo chown root:root /etc/init.d/homeassistant.sh
#
# If you want HA to start on boot also run the following:
# sudo update-rc.d homeassistant.sh defaults
# sudo update-rc.d homeassistant.sh enable
#
# You should now be able to start HA by running
# sudo /etc/init.d/homeassistant.sh start
### BEGIN INIT INFO
# Provides: myservice
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Put a short description of the service here
# Description: Put a long description of the service here
### END INIT INFO
# Change the next 3 lines to suit where you install your script and what you want to call it
DIR=/home/pi/Apps/home-assistant
DAEMON="/home/pi/.pyenv/shims/python3 -m homeassistant"
DAEMON_NAME=homeassistant
# Add any command line options for your daemon here
DAEMON_OPTS=""
# This next line determines what user the script runs as.
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
DAEMON_USER=pi
# The process ID of the script when it runs is stored here:
PIDFILE=/var/run/$DAEMON_NAME.pid
. /lib/lsb/init-functions
do_start () {
log_daemon_msg "Starting system $DAEMON_NAME daemon"
start-stop-daemon --start --background --chdir $DIR --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS
log_end_msg $?
}
do_stop () {
log_daemon_msg "Stopping system $DAEMON_NAME daemon"
start-stop-daemon --stop --pidfile $PIDFILE --retry 10
log_end_msg $?
}
case "$1" in
start|stop)
do_${1}
;;
restart|reload|force-reload)
do_stop
do_start
;;
status)
status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
;;
*)
echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
exit 1
;;
esac
exit 0

View File

@ -1,88 +0,0 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: homeassistant
# Required-Start: $local_fs $network $named $time $syslog
# Required-Stop: $local_fs $network $named $time $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Home\ Assistant
### END INIT INFO
# /etc/init.d Service Script for Home Assistant
# Created with: https://gist.github.com/naholyr/4275302#file-new-service-sh
#
# Installation:
# 1) Populate RUNAS and RUNDIR variables
# 2) Create Log -- sudo touch /var/log/homeassistant.log
# 3) Set Log Ownership -- sudo chown USER:GROUP /var/log/homeassistant.log
# 4) Create PID File -- sudo touch /var/run/homeassistant.pid
# 5) Set PID File Ownership -- sudo chown USER:GROUP /var/run/homeassistant.pid
# 6) Install init.d script -- cp homeassistant.daemon /etc/init.d/homeassistant
# 7) Setup HA for Auto Start -- sudo update-rc.d homeassistant defaults
# 8) Run HA -- sudo service homeassistant start
#
# After installation, HA should start automatically. If HA does not start,
# check the log file output for errors. (/var/log/homeassistant.log)
#
# For this script, it is assumed that you are using a local Virtual Environment
# per the install directions as http://home-assistant.io
# If you are not, the SCRIPT variable must be modified to point to the correct
# Python environment.
SCRIPT="source bin/activate; ./bin/python -m homeassistant"
RUNAS=<USER TO RUN SERVER AS>
RUNDIR=<LOCATION OF home-assistant DIR>
PIDFILE=/var/run/homeassistant.pid
LOGFILE=/var/log/homeassistant.log
start() {
if [ -f /var/run/$PIDNAME ] && kill -0 $(cat /var/run/$PIDNAME); then
echo 'Service already running' >&2
return 1
fi
echo 'Starting service…' >&2
local CMD="cd $RUNDIR; $SCRIPT &> \"$LOGFILE\" & echo \$!"
su -c "$CMD" $RUNAS > "$PIDFILE"
echo 'Service started' >&2
}
stop() {
if [ ! -f "$PIDFILE" ] || ! kill -0 $(cat "$PIDFILE"); then
echo 'Service not running' >&2
return 1
fi
echo 'Stopping service…' >&2
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
echo 'Service stopped' >&2
}
uninstall() {
echo -n "Are you really sure you want to uninstall this service? That cannot be undone. [yes|No] "
local SURE
read SURE
if [ "$SURE" = "yes" ]; then
stop
rm -f "$PIDFILE"
echo "Notice: log file is not be removed: '$LOGFILE'" >&2
update-rc.d -f homeassistant remove
rm -fv "$0"
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
uninstall)
uninstall
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart|uninstall}"
esac

View File

@ -1,6 +1,6 @@
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
git pull
echo "The update script has been deprecated since Home Assistant v0.7"
echo
echo "Home Assistant is now distributed via PyPi and can be installed and"
echo "upgraded by running: pip3 install --upgrade homeassistant"
echo
echo "If you are developing a new feature for Home Assistant, run: git pull"

View File

@ -7,7 +7,7 @@ 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)
'https://github.com/balloob/home-assistant/archive/{}.zip'.format(VERSION)
PACKAGES = find_packages() + \
['homeassistant.external', 'homeassistant.external.noop',

2
tests/__init__.py Normal file
View File

@ -0,0 +1,2 @@
import logging
logging.disable(logging.CRITICAL)

View File

@ -8,7 +8,7 @@ import os
from datetime import timedelta
from unittest import mock
import homeassistant.core as ha
from homeassistant import core as ha, loader
import homeassistant.util.location as location_util
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import ToggleEntity
@ -38,6 +38,9 @@ def get_test_home_assistant(num_threads=None):
hass.config.latitude = 32.87336
hass.config.longitude = -117.22743
# if not loader.PREPARED:
loader. prepare(hass)
return hass

View File

@ -21,7 +21,6 @@ class TestAutomationTime(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
loader.prepare(self.hass)
self.calls = []
def record_call(service):

View File

@ -7,6 +7,7 @@ Tests Home Assistant HTTP component does what it should do.
# pylint: disable=protected-access,too-many-public-methods
import unittest
import json
from unittest.mock import patch
import requests
@ -35,7 +36,9 @@ def _url(path=""):
return HTTP_BASE_URL + path
def setUpModule(): # pylint: disable=invalid-name
@patch('homeassistant.components.http.util.get_local_ip',
return_value='127.0.0.1')
def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
""" Initalizes a Home Assistant server. """
global hass

View File

@ -0,0 +1,111 @@
"""
tests.components.test_conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests Conversation component.
"""
# pylint: disable=too-many-public-methods,protected-access
import unittest
from unittest.mock import patch
import homeassistant.components as core_components
from homeassistant.components import conversation
from homeassistant.const import ATTR_ENTITY_ID
from tests.common import get_test_home_assistant
class TestConversation(unittest.TestCase):
""" Test the conversation component. """
def setUp(self): # pylint: disable=invalid-name
""" Start up ha for testing """
self.ent_id = 'light.kitchen_lights'
self.hass = get_test_home_assistant(3)
self.hass.states.set(self.ent_id, 'on')
self.assertTrue(core_components.setup(self.hass, {}))
self.assertTrue(
conversation.setup(self.hass, {conversation.DOMAIN: {}}))
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_turn_on(self):
""" Setup and perform good turn on requests """
calls = []
def record_call(service):
calls.append(service)
self.hass.services.register('light', 'turn_on', record_call)
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights on'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
call = calls[-1]
self.assertEqual('light', call.domain)
self.assertEqual('turn_on', call.service)
self.assertEqual([self.ent_id], call.data[ATTR_ENTITY_ID])
def test_turn_off(self):
""" Setup and perform good turn off requests """
calls = []
def record_call(service):
calls.append(service)
self.hass.services.register('light', 'turn_off', record_call)
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights off'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
call = calls[-1]
self.assertEqual('light', call.domain)
self.assertEqual('turn_off', call.service)
self.assertEqual([self.ent_id], call.data[ATTR_ENTITY_ID])
@patch('homeassistant.components.conversation.logging.Logger.error')
@patch('homeassistant.core.ServiceRegistry.call')
def test_bad_request_format(self, mock_logger, mock_call):
""" Setup and perform a badly formatted request """
event_data = {
conversation.ATTR_TEXT:
'what is the answer to the ultimate question of life, ' +
'the universe and everything'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
self.assertTrue(mock_logger.called)
self.assertFalse(mock_call.called)
@patch('homeassistant.components.conversation.logging.Logger.error')
@patch('homeassistant.core.ServiceRegistry.call')
def test_bad_request_entity(self, mock_logger, mock_call):
""" Setup and perform requests with bad entity id """
event_data = {conversation.ATTR_TEXT: 'turn something off'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
self.assertTrue(mock_logger.called)
self.assertFalse(mock_call.called)
@patch('homeassistant.components.conversation.logging.Logger.error')
@patch('homeassistant.core.ServiceRegistry.call')
def test_bad_request_command(self, mock_logger, mock_call):
""" Setup and perform requests with bad command """
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights over'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
self.assertTrue(mock_logger.called)
self.assertFalse(mock_call.called)
@patch('homeassistant.components.conversation.logging.Logger.error')
@patch('homeassistant.core.ServiceRegistry.call')
def test_bad_request_notext(self, mock_logger, mock_call):
""" Setup and perform requests with bad command with no text """
event_data = {}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
self.assertTrue(mock_logger.called)
self.assertFalse(mock_call.called)

View File

@ -15,8 +15,8 @@ from homeassistant.components import (
from tests.common import (
get_test_home_assistant, ensure_sun_risen, ensure_sun_set,
trigger_device_tracker_scan)
get_test_config_dir, get_test_home_assistant, ensure_sun_risen,
ensure_sun_set, trigger_device_tracker_scan)
KNOWN_DEV_PATH = None
@ -26,13 +26,8 @@ def setUpModule(): # pylint: disable=invalid-name
""" Initalizes a Home Assistant server. """
global KNOWN_DEV_PATH
hass = get_test_home_assistant()
loader.prepare(hass)
KNOWN_DEV_PATH = hass.config.path(
device_tracker.KNOWN_DEVICES_FILE)
hass.stop()
KNOWN_DEV_PATH = os.path.join(get_test_config_dir(),
device_tracker.KNOWN_DEVICES_FILE)
with open(KNOWN_DEV_PATH, 'w') as fil:
fil.write('device,name,track,picture\n')

View File

@ -7,7 +7,6 @@ Tests the device tracker compoments.
# pylint: disable=protected-access,too-many-public-methods
import unittest
from datetime import timedelta
import logging
import os
import homeassistant.core as ha
@ -21,18 +20,12 @@ import homeassistant.components.device_tracker as device_tracker
from tests.common import get_test_home_assistant
def setUpModule(): # pylint: disable=invalid-name
""" Setup to ignore group errors. """
logging.disable(logging.CRITICAL)
class TestComponentsDeviceTracker(unittest.TestCase):
""" Tests homeassistant.components.device_tracker module. """
def setUp(self): # pylint: disable=invalid-name
""" Init needed objects. """
self.hass = get_test_home_assistant()
loader.prepare(self.hass)
self.known_dev_path = self.hass.config.path(
device_tracker.KNOWN_DEVICES_FILE)

View File

@ -7,6 +7,7 @@ Tests Home Assistant HTTP component does what it should do.
# pylint: disable=protected-access,too-many-public-methods
import re
import unittest
from unittest.mock import patch
import requests
@ -34,7 +35,9 @@ def _url(path=""):
return HTTP_BASE_URL + path
def setUpModule(): # pylint: disable=invalid-name
@patch('homeassistant.components.http.util.get_local_ip',
return_value='127.0.0.1')
def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
""" Initalizes a Home Assistant server. """
global hass

View File

@ -199,7 +199,7 @@ class TestComponentsGroup(unittest.TestCase):
self.hass,
{
group.DOMAIN: {
'second_group': self.group_entity_id + ',light.Bowl'
'second_group': 'light.Bowl, ' + self.group_entity_id
}
}))
@ -207,6 +207,8 @@ class TestComponentsGroup(unittest.TestCase):
group.ENTITY_ID_FORMAT.format('second_group'))
self.assertEqual(STATE_ON, group_state.state)
self.assertEqual(set((self.group_entity_id, 'light.bowl')),
set(group_state.attributes['entity_id']))
self.assertFalse(group_state.attributes[group.ATTR_AUTO])
def test_groups_get_unique_names(self):

View File

@ -20,7 +20,6 @@ class TestComponentsCore(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
""" Init needed objects. """
self.hass = ha.HomeAssistant()
loader.prepare(self.hass)
self.assertTrue(comps.setup(self.hass, {}))
self.hass.states.set('light.Bowl', STATE_ON)

View File

@ -23,7 +23,6 @@ class TestLight(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
self.hass = get_test_home_assistant()
loader.prepare(self.hass)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """

View File

@ -5,7 +5,6 @@ tests.test_component_media_player
Tests media_player component.
"""
# pylint: disable=too-many-public-methods,protected-access
import logging
import unittest
import homeassistant.core as ha
@ -18,11 +17,6 @@ import homeassistant.components.media_player as media_player
from tests.common import mock_service
def setUpModule(): # pylint: disable=invalid-name
""" Setup to ignore media_player errors. """
logging.disable(logging.CRITICAL)
class TestMediaPlayer(unittest.TestCase):
""" Test the media_player module. """

View File

@ -77,7 +77,7 @@ class TestSun(unittest.TestCase):
""" Test if the state changes at next setting/rising. """
self.hass.config.latitude = '32.87336'
self.hass.config.longitude = '117.22743'
sun.setup(self.hass, {})
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
if sun.is_on(self.hass):
test_state = sun.STATE_BELOW_HORIZON

View File

@ -19,7 +19,6 @@ class TestSwitch(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
self.hass = get_test_home_assistant()
loader.prepare(self.hass)
platform = loader.get_component('switch.test')

View File

@ -21,7 +21,6 @@ class TestComponentsCore(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
""" Init needed objects. """
self.hass = get_test_home_assistant()
loader.prepare(self.hass)
self.hass.states.set('light.Bowl', STATE_ON)
self.hass.states.set('light.Ceiling', STATE_OFF)

View File

@ -49,13 +49,15 @@ class TestConfig(unittest.TestCase):
self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR))
def test_ensure_config_exists_creates_config(self):
@mock.patch('builtins.print')
def test_ensure_config_exists_creates_config(self, mock_print):
""" Test that calling ensure_config_exists creates a new config file if
none exists. """
config_util.ensure_config_exists(CONFIG_DIR, False)
self.assertTrue(os.path.isfile(YAML_PATH))
self.assertTrue(mock_print.called)
def test_ensure_config_exists_uses_existing_config(self):
""" Test that calling ensure_config_exists uses existing config. """
@ -100,11 +102,12 @@ class TestConfig(unittest.TestCase):
self.assertEqual({'hello': 'world'},
config_util.load_config_file(YAML_PATH))
def test_create_default_config_detect_location(self):
@mock.patch('homeassistant.util.location.detect_location_info',
mock_detect_location_info)
@mock.patch('builtins.print')
def test_create_default_config_detect_location(self, mock_print):
""" Test that detect location sets the correct config keys. """
with mock.patch('homeassistant.util.location.detect_location_info',
mock_detect_location_info):
config_util.ensure_config_exists(CONFIG_DIR)
config_util.ensure_config_exists(CONFIG_DIR)
config = config_util.load_config_file(YAML_PATH)
@ -121,11 +124,15 @@ class TestConfig(unittest.TestCase):
}
self.assertEqual(expected_values, ha_conf)
self.assertTrue(mock_print.called)
def test_create_default_config_returns_none_if_write_error(self):
@mock.patch('builtins.print')
def test_create_default_config_returns_none_if_write_error(self,
mock_print):
"""
Test that writing default config to non existing folder returns None.
"""
self.assertIsNone(
config_util.create_default_config(
os.path.join(CONFIG_DIR, 'non_existing_dir/'), False))
self.assertTrue(mock_print.called)

View File

@ -17,7 +17,6 @@ class TestLoader(unittest.TestCase):
""" Test the loader module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = get_test_home_assistant()
loader.prepare(self.hass)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """

View File

@ -8,6 +8,7 @@ Uses port 8125 as a port that nothing runs on
"""
# pylint: disable=protected-access,too-many-public-methods
import unittest
from unittest.mock import patch
import homeassistant.core as ha
import homeassistant.bootstrap as bootstrap
@ -29,7 +30,9 @@ def _url(path=""):
return HTTP_BASE_URL + path
def setUpModule(): # pylint: disable=invalid-name
@patch('homeassistant.components.http.util.get_local_ip',
return_value='127.0.0.1')
def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
""" Initalizes a Home Assistant server and Slave instance. """
global hass, slave, master_api, broken_api
@ -51,6 +54,10 @@ def setUpModule(): # pylint: disable=invalid-name
# Start slave
slave = remote.HomeAssistant(master_api)
bootstrap.setup_component(
slave, http.DOMAIN,
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
http.CONF_SERVER_PORT: 8130}})
slave.start()