mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
commit
3bbdc5bcd7
@ -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
2
.gitignore
vendored
@ -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
|
||||
|
@ -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/* && \
|
||||
|
37
README.md
37
README.md
@ -1,15 +1,23 @@
|
||||
# Home Assistant [](https://travis-ci.org/balloob/home-assistant) [](https://coveralls.io/r/balloob/home-assistant?branch=master) [](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.
|
||||
|
||||
[](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.
|
||||
|
||||
[][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 |
@ -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):
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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]}
|
||||
|
@ -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.",
|
||||
|
154
homeassistant/components/device_tracker/aruba.py
Normal file
154
homeassistant/components/device_tracker/aruba.py
Normal 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
|
@ -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
|
||||
|
@ -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 }}' />
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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])
|
||||
])
|
||||
|
||||
|
||||
|
@ -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 | \
|
||||
|
@ -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. """
|
||||
|
@ -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. """
|
||||
|
@ -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)
|
||||
])
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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. """
|
||||
|
@ -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
101
scripts/hass-daemon
Normal 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
|
@ -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
|
@ -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
|
@ -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"
|
||||
|
2
setup.py
2
setup.py
@ -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
2
tests/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
@ -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
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
111
tests/components/test_conversation.py
Normal file
111
tests/components/test_conversation.py
Normal 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)
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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. """
|
||||
|
@ -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. """
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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. """
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user