merge from dev

This commit is contained in:
Per Sandström 2015-09-13 21:00:51 +02:00
commit 964a1f9aef
169 changed files with 3983 additions and 1830 deletions

View File

@ -4,8 +4,6 @@ source = homeassistant
omit =
homeassistant/__main__.py
homeassistant/external/*
# omit pieces of code that rely on external devices being present
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
@ -28,14 +26,17 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/ifttt.py
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
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/discovery.py
@ -44,9 +45,12 @@ omit =
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/sonos.py
homeassistant/components/notify/file.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/nma.py
@ -56,7 +60,9 @@ omit =
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/mysensors.py
@ -69,6 +75,7 @@ omit =
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/transmission.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/command_switch.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.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

9
.gitmodules vendored
View File

@ -1,12 +1,3 @@
[submodule "homeassistant/external/noop"]
path = homeassistant/external/noop
url = https://github.com/balloob/noop.git
[submodule "homeassistant/external/vera"]
path = homeassistant/external/vera
url = https://github.com/jamespcole/home-assistant-vera-api.git
[submodule "homeassistant/external/nzbclients"]
path = homeassistant/external/nzbclients
url = https://github.com/jamespcole/home-assistant-nzb-clients.git
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
path = homeassistant/components/frontend/www_static/home-assistant-polymer
url = https://github.com/balloob/home-assistant-polymer.git

View File

@ -3,10 +3,10 @@ language: python
python:
- "3.4"
install:
- pip install -r requirements.txt
- pip install -r requirements_all.txt
- pip install flake8 pylint coveralls
script:
- flake8 homeassistant --exclude bower_components,external
- flake8 homeassistant
- pylint homeassistant
- coverage run -m unittest discover tests
after_success:

View File

@ -3,6 +3,14 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
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/* && \

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
recursive-exclude tests *

View File

@ -1,16 +1,24 @@
# 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
* [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/)
* [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), [Kodi (XBMC)](http://kodi.tv/), and iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api))
* 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).
* [See full list of supported devices](https://home-assistant.io/components/)
@ -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.

View File

@ -12,7 +12,7 @@ Example component to target an entity_id to:
Configuration:
To use the Example custom component you will need to add the following to
your config/configuration.yaml
your configuration.yaml file.
example:
target: TARGET_ENTITY

View File

@ -1,16 +1,14 @@
"""
custom_components.hello_world
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements the bare minimum that a component should implement.
Configuration:
To use the hello_word component you will need to add the following to your
config/configuration.yaml
configuration.yaml file.
hello_world:
"""
# The domain of your component. Should be equal to the name of your component

View File

@ -1,7 +1,6 @@
"""
custom_components.mqtt_example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows how to communicate with MQTT. Follows a topic on MQTT and updates the
state of an entity to the last message received on that topic.
@ -12,7 +11,7 @@ example payload {"new_state": "some new state"}.
Configuration:
To use the mqtt_example component you will need to add the following to your
config/configuration.yaml
configuration.yaml file.
mqtt_example:
topic: home-assistant/mqtt_example

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

@ -4,12 +4,10 @@ from __future__ import print_function
import sys
import os
import argparse
import subprocess
import importlib
DEPENDENCIES = ['requests>=2.0', 'pyyaml>=3.11', 'pytz>=2015.2']
IS_VIRTUAL = (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or
hasattr(sys, 'real_prefix'))
from homeassistant import bootstrap
import homeassistant.config as config_util
from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START
def validate_python():
@ -18,106 +16,58 @@ def validate_python():
if major < 3 or (major == 3 and minor < 4):
print("Home Assistant requires atleast Python 3.4")
sys.exit()
def ensure_pip():
""" Validate pip is installed so we can install packages on demand. """
if importlib.find_loader('pip') is None:
print("Your Python installation did not bundle 'pip'")
print("Home Assistant requires 'pip' to be installed.")
print("Please install pip: "
"https://pip.pypa.io/en/latest/installing.html")
sys.exit()
# Copy of homeassistant.util.package because we can't import yet
def install_package(package):
"""Install a package on PyPi. Accepts pip compatible package strings.
Return boolean if install successfull."""
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
if not IS_VIRTUAL:
args.append('--user')
try:
return 0 == subprocess.call(args)
except subprocess.SubprocessError:
return False
def validate_dependencies():
""" Validate all dependencies that HA uses. """
ensure_pip()
print("Validating dependencies...")
import_fail = False
for requirement in DEPENDENCIES:
if not install_package(requirement):
import_fail = True
print('Fatal Error: Unable to install dependency', requirement)
if import_fail:
print(("Install dependencies by running: "
"python3 -m pip install -r requirements.txt"))
sys.exit()
def ensure_path_and_load_bootstrap():
""" Ensure sys load path is correct and load Home Assistant bootstrap. """
try:
from homeassistant import bootstrap
except ImportError:
# This is to add support to load Home Assistant using
# `python3 homeassistant` instead of `python3 -m homeassistant`
# Insert the parent directory of this file into the module search path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from homeassistant import bootstrap
return bootstrap
def validate_git_submodules():
""" Validate the git submodules are cloned. """
try:
# pylint: disable=no-name-in-module, unused-variable
from homeassistant.external.noop import WORKING # noqa
except ImportError:
print("Repository submodules have not been initialized")
print("Please run: git submodule update --init --recursive")
sys.exit()
sys.exit(1)
def ensure_config_path(config_dir):
""" Gets the path to the configuration file.
Creates one if it not exists. """
""" Validates configuration directory. """
lib_dir = os.path.join(config_dir, 'lib')
# Test if configuration directory exists
if not os.path.isdir(config_dir):
print(('Fatal Error: Unable to find specified configuration '
'directory {} ').format(config_dir))
sys.exit()
if config_dir != config_util.get_default_config_dir():
print(('Fatal Error: Specified configuration directory does '
'not exist {} ').format(config_dir))
sys.exit(1)
import homeassistant.config as config_util
try:
os.mkdir(config_dir)
except OSError:
print(('Fatal Error: Unable to create default configuration '
'directory {} ').format(config_dir))
sys.exit(1)
# Test if library directory exists
if not os.path.isdir(lib_dir):
try:
os.mkdir(lib_dir)
except OSError:
print(('Fatal Error: Unable to create library '
'directory {} ').format(lib_dir))
sys.exit(1)
def ensure_config_file(config_dir):
""" Ensure configuration file exists. """
config_path = config_util.ensure_config_exists(config_dir)
if config_path is None:
print('Error getting configuration path')
sys.exit()
sys.exit(1)
return config_path
def get_arguments():
""" Get parsed passed in arguments. """
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.")
parser.add_argument('--version', action='version', version=__version__)
parser.add_argument(
'-c', '--config',
metavar='path_to_config_dir',
default="config",
default=config_util.get_default_config_dir(),
help="Directory that contains the Home Assistant configuration")
parser.add_argument(
'--demo-mode',
@ -127,40 +77,115 @@ def get_arguments():
'--open-ui',
action='store_true',
help='Open the webinterface in a browser')
parser.add_argument(
'--skip-pip',
action='store_true',
help='Skips pip install of required packages on startup')
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')
parser.add_argument(
'--log-rotate-days',
type=int,
default=None,
help='Enables daily log rotation and keeps up to the specified days')
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()
validate_dependencies()
# Windows needs this to pick up new modules
importlib.invalidate_caches()
bootstrap = ensure_path_and_load_bootstrap()
validate_git_submodules()
args = get_arguments()
config_dir = os.path.join(os.getcwd(), args.config)
config_path = ensure_config_path(config_dir)
ensure_config_path(config_dir)
# 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:
from homeassistant.components import frontend, demo
hass = bootstrap.from_config_dict({
frontend.DOMAIN: {},
demo.DOMAIN: {}
})
config = {
'frontend': {},
'demo': {}
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
else:
hass = bootstrap.from_config_file(config_path)
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
if args.open_ui:
from homeassistant.const import EVENT_HOMEASSISTANT_START
def open_browser(event):
""" Open the webinterface in a browser. """
if hass.config.api is not None:

View File

@ -10,7 +10,9 @@ start by calling homeassistant.start_home_assistant(bus)
"""
import os
import sys
import logging
import logging.handlers
from collections import defaultdict
import homeassistant.core as core
@ -52,22 +54,19 @@ 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
return True
def _handle_requirements(component, name):
def _handle_requirements(hass, component, name):
""" Installs requirements for component. """
if not hasattr(component, 'REQUIREMENTS'):
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS:
if not pkg_util.install_package(req):
if not pkg_util.install_package(req, target=hass.config.path('lib')):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
return False
@ -77,6 +76,8 @@ def _handle_requirements(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
@ -88,7 +89,7 @@ def _setup_component(hass, domain, config):
domain, ", ".join(missing_deps))
return False
if not _handle_requirements(component, domain):
if not _handle_requirements(hass, component, domain):
return False
try:
@ -122,6 +123,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
# Not found
if platform is None:
_LOGGER.error('Unable to find platform %s', platform_path)
return None
# Already loaded
@ -138,14 +140,21 @@ def prepare_setup_platform(hass, config, domain, platform_name):
component)
return None
if not _handle_requirements(platform, platform_path):
if not _handle_requirements(hass, platform, platform_path):
return None
return platform
# pylint: disable=too-many-branches, too-many-statements
def from_config_dict(config, hass=None):
def mount_local_lib_path(config_dir):
""" Add local library to Python Path """
sys.path.insert(0, os.path.join(config_dir, 'lib'))
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False, skip_pip=False,
log_rotate_days=None):
"""
Tries to configure Home Assistant from a config dict.
@ -153,10 +162,20 @@ def from_config_dict(config, hass=None):
"""
if hass is None:
hass = core.HomeAssistant()
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
process_ha_core_config(hass, config.get(core.DOMAIN, {}))
enable_logging(hass)
if enable_log:
enable_logging(hass, verbose, daemon, log_rotate_days)
hass.config.skip_pip = skip_pip
if skip_pip:
_LOGGER.warning('Skipping pip installation of required modules. '
'This may cause issues.')
_ensure_loader_prepared(hass)
@ -185,7 +204,8 @@ def from_config_dict(config, hass=None):
return hass
def from_config_file(config_path, hass=None):
def from_config_file(config_path, hass=None, verbose=False, daemon=False,
skip_pip=True, log_rotate_days=None):
"""
Reads the configuration file and tries to start all the required
functionality. Will add functionality to 'hass' parameter if given,
@ -195,35 +215,41 @@ def from_config_file(config_path, hass=None):
hass = core.HomeAssistant()
# Set config dir to directory holding config file
hass.config.config_dir = os.path.abspath(os.path.dirname(config_path))
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
enable_logging(hass, verbose, daemon, log_rotate_days)
config_dict = config_util.load_config_file(config_path)
return from_config_dict(config_dict, hass)
return from_config_dict(config_dict, hass, enable_log=False,
skip_pip=skip_pip)
def enable_logging(hass):
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
""" 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')
@ -234,14 +260,20 @@ def enable_logging(hass):
if (err_path_exists and os.access(err_log_path, os.W_OK)) or \
(not err_path_exists and os.access(hass.config.config_dir, os.W_OK)):
err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True)
if log_rotate_days:
err_handler = logging.handlers.TimedRotatingFileHandler(
err_log_path, when='midnight', backupCount=log_rotate_days)
else:
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

@ -7,7 +7,7 @@ runs with the Firmata firmware.
Configuration:
To use the Arduino board you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
arduino:
port: /dev/ttyACM0

View File

@ -1,4 +1,6 @@
"""
homeassistant.components.camera.generic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras.
This component provides basic support for IP cameras. For the basic support to
@ -7,11 +9,11 @@ need to specify the "still_image_url" parameter which should be the location of
the JPEG image.
As part of the basic support the following features will be provided:
-MJPEG video streaming
-Saving a snapshot
-Recording(JPEG frame capture)
- MJPEG video streaming
- Saving a snapshot
- Recording(JPEG frame capture)
To use this component, add the following to your config/configuration.yaml:
To use this component, add the following to your configuration.yaml file.
camera:
platform: generic
@ -20,29 +22,24 @@ camera:
password: YOUR_PASSWORD
still_image_url: http://YOUR_CAMERA_IP_AND_PORT/image.jpg
VARIABLES:
These are the variables for the device_data array:
Variables:
still_image_url
*Required
The URL your camera serves the image on.
Example: http://192.168.1.21:2112/
The URL your camera serves the image on, eg. http://192.168.1.21:2112/
name
*Optional
This parameter allows you to override the name of your camera in homeassistant
This parameter allows you to override the name of your camera in Home
Assistant.
username
*Optional
THe username for acessing your camera
The username for accessing your camera.
password
*Optional
the password for accessing your camera
The password for accessing your camera.
"""
import logging
from requests.auth import HTTPBasicAuth
@ -78,7 +75,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info['still_image_url']
def camera_image(self):
""" Return a still image reponse from the camera """
""" Return a still image reponse from the camera. """
if self._username and self._password:
response = requests.get(
self._still_image_url,
@ -90,5 +87,5 @@ class GenericCamera(Camera):
@property
def name(self):
""" Return the name of this device """
""" Return the name of this device. """
return self._name

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

@ -14,10 +14,10 @@ from homeassistant.const import (
DOMAIN = "demo"
DEPENDENCIES = []
DEPENDENCIES = ['introduction', 'conversation']
COMPONENTS_WITH_DEMO_PLATFORM = [
'switch', 'light', 'thermostat', 'sensor', 'media_player', 'notify']
'switch', 'light', 'sensor', 'thermostat', 'media_player', 'notify']
def setup(hass, config):
@ -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

@ -1,51 +1,82 @@
"""
homeassistant.components.tracker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
homeassistant.components.device_tracker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to keep track of devices.
device_tracker:
platform: netgear
# Optional
# How many seconds to wait after not seeing device to consider it not home
consider_home: 180
# Seconds between each scan
interval_seconds: 12
# New found devices auto found
track_new_devices: yes
"""
import logging
import threading
import os
import csv
from datetime import timedelta
import logging
import os
import threading
from homeassistant.loader import get_component
from homeassistant.helpers import validate_config
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.components import discovery, group
from homeassistant.config import load_yaml_config_file
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform
from homeassistant.helpers.entity import Entity
import homeassistant.util as util
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM, DEVICE_DEFAULT_NAME)
from homeassistant.components import group
ATTR_ENTITY_PICTURE, DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME)
DOMAIN = "device_tracker"
DEPENDENCIES = []
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
GROUP_NAME_ALL_DEVICES = 'all devices'
ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# After how much time do we consider a device not home if
# it does not show up on scans
TIME_DEVICE_NOT_FOUND = timedelta(minutes=3)
CSV_DEVICES = "known_devices.csv"
YAML_DEVICES = 'known_devices.yaml'
# Filename to save known devices to
KNOWN_DEVICES_FILE = "known_devices.csv"
CONF_TRACK_NEW = "track_new_devices"
DEFAULT_CONF_TRACK_NEW = True
CONF_SECONDS = "interval_seconds"
CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONF_CONSIDER_HOME = 180 # seconds
DEFAULT_CONF_SECONDS = 12
CONF_SCAN_INTERVAL = "interval_seconds"
DEFAULT_SCAN_INTERVAL = 12
TRACK_NEW_DEVICES = "track_new_devices"
CONF_AWAY_HIDE = 'hide_if_away'
DEFAULT_AWAY_HIDE = False
SERVICE_SEE = 'see'
ATTR_LATITUDE = 'latitude'
ATTR_LONGITUDE = 'longitude'
ATTR_MAC = 'mac'
ATTR_DEV_ID = 'dev_id'
ATTR_HOST_NAME = 'host_name'
ATTR_LOCATION_NAME = 'location_name'
ATTR_GPS = 'gps'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_NETGEAR: 'netgear',
}
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-arguments
def is_on(hass, entity_id=None):
""" Returns if any or specified device is home. """
@ -54,290 +85,309 @@ def is_on(hass, entity_id=None):
return hass.states.is_state(entity, STATE_HOME)
def see(hass, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None):
""" Call service to notify you see device. """
data = {key: value for key, value in
((ATTR_MAC, mac),
(ATTR_DEV_ID, dev_id),
(ATTR_HOST_NAME, host_name),
(ATTR_LOCATION_NAME, location_name),
(ATTR_GPS, gps)) if value is not None}
hass.services.call(DOMAIN, SERVICE_SEE, data)
def setup(hass, config):
""" Sets up the device tracker. """
""" Setup device tracker """
yaml_path = hass.config.path(YAML_DEVICES)
csv_path = hass.config.path(CSV_DEVICES)
if os.path.isfile(csv_path) and not os.path.isfile(yaml_path) and \
convert_csv_config(csv_path, yaml_path):
os.remove(csv_path)
if not validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER):
return False
conf = config.get(DOMAIN, {})
consider_home = util.convert(conf.get(CONF_CONSIDER_HOME), int,
DEFAULT_CONF_CONSIDER_HOME)
track_new = util.convert(conf.get(CONF_TRACK_NEW), bool,
DEFAULT_CONF_TRACK_NEW)
tracker_type = config[DOMAIN].get(CONF_PLATFORM)
devices = load_config(yaml_path, hass, timedelta(seconds=consider_home))
tracker = DeviceTracker(hass, consider_home, track_new, devices)
tracker_implementation = get_component(
'device_tracker.{}'.format(tracker_type))
if tracker_implementation is None:
_LOGGER.error("Unknown device_tracker type specified: %s.",
tracker_type)
return False
device_scanner = tracker_implementation.get_scanner(hass, config)
if device_scanner is None:
_LOGGER.error("Failed to initialize device scanner: %s",
tracker_type)
return False
seconds = util.convert(config[DOMAIN].get(CONF_SECONDS), int,
DEFAULT_CONF_SECONDS)
track_new_devices = config[DOMAIN].get(TRACK_NEW_DEVICES) or False
_LOGGER.info("Tracking new devices: %s", track_new_devices)
tracker = DeviceTracker(hass, device_scanner, seconds, track_new_devices)
# We only succeeded if we got to parse the known devices file
return not tracker.invalid_known_devices_file
class DeviceTracker(object):
""" Class that tracks which devices are home and which are not. """
def __init__(self, hass, device_scanner, seconds, track_new_devices):
self.hass = hass
self.device_scanner = device_scanner
self.lock = threading.Lock()
# Do we track new devices by default?
self.track_new_devices = track_new_devices
# Dictionary to keep track of known devices and devices we track
self.tracked = {}
self.untracked_devices = set()
# Did we encounter an invalid known devices file
self.invalid_known_devices_file = False
# Wrap it in a func instead of lambda so it can be identified in
# the bus by its __name__ attribute.
def update_device_state(now):
""" Triggers update of the device states. """
self.update_devices(now)
dev_group = group.Group(
hass, GROUP_NAME_ALL_DEVICES, user_defined=False)
def reload_known_devices_service(service):
""" Reload known devices file. """
self._read_known_devices_file()
self.update_devices(dt_util.utcnow())
dev_group.update_tracked_entity_ids(self.device_entity_ids)
reload_known_devices_service(None)
if self.invalid_known_devices_file:
return
seconds = range(0, 60, seconds)
_LOGGER.info("Device tracker interval second=%s", seconds)
track_utc_time_change(hass, update_device_state, second=seconds)
hass.services.register(DOMAIN,
SERVICE_DEVICE_TRACKER_RELOAD,
reload_known_devices_service)
@property
def device_entity_ids(self):
""" Returns a set containing all device entity ids
that are being tracked. """
return set(device['entity_id'] for device in self.tracked.values())
def _update_state(self, now, device, is_home):
""" Update the state of a device. """
dev_info = self.tracked[device]
if is_home:
# Update last seen if at home
dev_info['last_seen'] = now
else:
# State remains at home if it has been seen in the last
# TIME_DEVICE_NOT_FOUND
is_home = now - dev_info['last_seen'] < TIME_DEVICE_NOT_FOUND
state = STATE_HOME if is_home else STATE_NOT_HOME
self.hass.states.set(
dev_info['entity_id'], state,
dev_info['state_attr'])
def update_devices(self, now):
""" Update device states based on the found devices. """
if not self.lock.acquire(False):
def setup_platform(p_type, p_config, disc_info=None):
""" Setup a device tracker platform. """
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
if platform is None:
return
try:
found_devices = set(dev.upper() for dev in
self.device_scanner.scan_devices())
if hasattr(platform, 'get_scanner'):
scanner = platform.get_scanner(hass, {DOMAIN: p_config})
for device in self.tracked:
is_home = device in found_devices
if scanner is None:
_LOGGER.error('Error setting up platform %s', p_type)
return
self._update_state(now, device, is_home)
setup_scanner_platform(hass, p_config, scanner, tracker.see)
return
if is_home:
found_devices.remove(device)
if not platform.setup_scanner(hass, p_config, tracker.see):
_LOGGER.error('Error setting up platform %s', p_type)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up platform %s', p_type)
# Did we find any devices that we didn't know about yet?
new_devices = found_devices - self.untracked_devices
for p_type, p_config in \
config_per_platform(config, DOMAIN, _LOGGER):
setup_platform(p_type, p_config)
if new_devices:
if not self.track_new_devices:
self.untracked_devices.update(new_devices)
def device_tracker_discovered(service, info):
""" Called when a device tracker platform is discovered. """
setup_platform(DISCOVERY_PLATFORMS[service], {}, info)
self._update_known_devices_file(new_devices)
finally:
self.lock.release()
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(),
device_tracker_discovered)
# pylint: disable=too-many-branches
def _read_known_devices_file(self):
""" Parse and process the known devices file. """
known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE)
def update_stale(now):
""" Clean up stale devices. """
tracker.update_stale(now)
track_utc_time_change(hass, update_stale, second=range(0, 60, 5))
# Return if no known devices file exists
if not os.path.isfile(known_dev_path):
tracker.setup_group()
def see_service(call):
""" Service to see a device. """
args = {key: value for key, value in call.data.items() if key in
(ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
ATTR_GPS)}
tracker.see(**args)
hass.services.register(DOMAIN, SERVICE_SEE, see_service)
return True
class DeviceTracker(object):
""" Track devices """
def __init__(self, hass, consider_home, track_new, devices):
self.hass = hass
self.devices = {dev.dev_id: dev for dev in devices}
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
self.consider_home = timedelta(seconds=consider_home)
self.track_new = track_new
self.lock = threading.Lock()
entity_ids = []
for device in devices:
if device.track:
entity_ids.append(device.entity_id)
device.update_ha_state()
self.group = None
def see(self, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None):
""" Notify device tracker that you see a device. """
with self.lock:
if mac is None and dev_id is None:
raise HomeAssistantError('Neither mac or device id passed in')
elif mac is not None:
mac = mac.upper()
device = self.mac_to_dev.get(mac)
if not device:
dev_id = util.slugify(host_name or mac)
else:
dev_id = str(dev_id)
device = self.devices.get(dev_id)
if device:
device.seen(host_name, location_name, gps)
if device.track:
device.update_ha_state()
return
# If no device can be found, create it
device = Device(
self.hass, self.consider_home, self.track_new, dev_id, mac,
(host_name or dev_id).replace('_', ' '))
self.devices[dev_id] = device
if mac is not None:
self.mac_to_dev[mac] = device
device.seen(host_name, location_name, gps)
if device.track:
device.update_ha_state()
# During init, we ignore the group
if self.group is not None:
self.group.update_tracked_entity_ids(
list(self.group.tracking) + [device.entity_id])
update_config(self.hass.config.path(YAML_DEVICES), dev_id, device)
def setup_group(self):
""" Initializes group for all tracked devices. """
entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track)
self.group = group.setup_group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
def update_stale(self, now):
""" Update stale devices. """
with self.lock:
for device in self.devices.values():
if device.last_update_home and device.stale(now):
device.update_ha_state(True)
class Device(Entity):
""" Tracked device. """
# pylint: disable=too-many-instance-attributes, too-many-arguments
host_name = None
location_name = None
gps = None
last_seen = None
# Track if the last update of this device was HOME
last_update_home = False
_state = STATE_NOT_HOME
def __init__(self, hass, consider_home, track, dev_id, mac, name=None,
picture=None, away_hide=False):
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
# Timedelta object how long we consider a device home if it is not
# detected anymore.
self.consider_home = consider_home
# Device ID
self.dev_id = dev_id
self.mac = mac
# If we should track this device
self.track = track
# Configured name
self.config_name = name
# Configured picture
self.config_picture = picture
self.away_hide = away_hide
@property
def name(self):
""" Returns the name of the entity. """
return self.config_name or self.host_name or DEVICE_DEFAULT_NAME
@property
def state(self):
""" State of the device. """
return self._state
@property
def state_attributes(self):
""" Device state attributes. """
attr = {}
if self.config_picture:
attr[ATTR_ENTITY_PICTURE] = self.config_picture
if self.gps:
attr[ATTR_LATITUDE] = self.gps[0],
attr[ATTR_LONGITUDE] = self.gps[1],
return attr
@property
def hidden(self):
""" If device should be hidden. """
return self.away_hide and self.state != STATE_HOME
def seen(self, host_name=None, location_name=None, gps=None):
""" Mark the device as seen. """
self.last_seen = dt_util.utcnow()
self.host_name = host_name
self.location_name = location_name
self.gps = gps
self.update()
def stale(self, now=None):
""" Return if device state is stale. """
return self.last_seen and \
(now or dt_util.utcnow()) - self.last_seen > self.consider_home
def update(self):
""" Update state of entity. """
if not self.last_seen:
return
elif self.location_name:
self._state = self.location_name
elif self.stale():
self._state = STATE_NOT_HOME
self.last_update_home = False
else:
self._state = STATE_HOME
self.last_update_home = True
self.lock.acquire()
self.untracked_devices.clear()
def convert_csv_config(csv_path, yaml_path):
""" Convert CSV config file format to YAML. """
used_ids = set()
with open(csv_path) as inp:
for row in csv.DictReader(inp):
dev_id = util.ensure_unique_string(
util.slugify(row['name']) or DEVICE_DEFAULT_NAME, used_ids)
used_ids.add(dev_id)
device = Device(None, None, row['track'] == '1', dev_id,
row['device'], row['name'], row['picture'])
update_config(yaml_path, dev_id, device)
return True
with open(known_dev_path) as inp:
# To track which devices need an entity_id assigned
need_entity_id = []
def load_config(path, hass, consider_home):
""" Load devices from YAML config file. """
if not os.path.isfile(path):
return []
return [
Device(hass, consider_home, device.get('track', False),
str(dev_id), device.get('mac'), device.get('name'),
device.get('picture'), device.get(CONF_AWAY_HIDE, False))
for dev_id, device in load_yaml_config_file(path).items()]
# All devices that are still in this set after we read the CSV file
# have been removed from the file and thus need to be cleaned up.
removed_devices = set(self.tracked.keys())
try:
for row in csv.DictReader(inp):
device = row['device'].upper()
def setup_scanner_platform(hass, config, scanner, see_device):
""" Helper method to connect scanner-based platform to device tracker. """
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
DEFAULT_SCAN_INTERVAL)
if row['track'] == '1':
if device in self.tracked:
# Device exists
removed_devices.remove(device)
else:
# We found a new device
need_entity_id.append(device)
# Initial scan of each mac we also tell about host name for config
seen = set()
self._track_device(device, row['name'])
def device_tracker_scan(now):
""" Called when interval matches. """
for mac in scanner.scan_devices():
if mac in seen:
host_name = None
else:
host_name = scanner.get_device_name(mac)
seen.add(mac)
see_device(mac=mac, host_name=host_name)
# Update state_attr with latest from file
state_attr = {
ATTR_FRIENDLY_NAME: row['name']
}
track_utc_time_change(hass, device_tracker_scan, second=range(0, 60,
interval))
if row['picture']:
state_attr[ATTR_ENTITY_PICTURE] = row['picture']
device_tracker_scan(None)
self.tracked[device]['state_attr'] = state_attr
else:
self.untracked_devices.add(device)
def update_config(path, dev_id, device):
""" Add device to YAML config file. """
with open(path, 'a') as out:
out.write('\n')
out.write('{}:\n'.format(device.dev_id))
# Remove existing devices that we no longer track
for device in removed_devices:
entity_id = self.tracked[device]['entity_id']
_LOGGER.info("Removing entity %s", entity_id)
self.hass.states.remove(entity_id)
self.tracked.pop(device)
self._generate_entity_ids(need_entity_id)
if not self.tracked:
_LOGGER.warning(
"No devices to track. Please update %s.",
known_dev_path)
_LOGGER.info("Loaded devices from %s", known_dev_path)
except KeyError:
self.invalid_known_devices_file = True
_LOGGER.warning(
("Invalid known devices file: %s. "
"We won't update it with new found devices."),
known_dev_path)
finally:
self.lock.release()
def _update_known_devices_file(self, new_devices):
""" Add new devices to known devices file. """
if not self.invalid_known_devices_file:
known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE)
try:
# If file does not exist we will write the header too
is_new_file = not os.path.isfile(known_dev_path)
with open(known_dev_path, 'a') as outp:
_LOGGER.info("Found %d new devices, updating %s",
len(new_devices), known_dev_path)
writer = csv.writer(outp)
if is_new_file:
writer.writerow(("device", "name", "track", "picture"))
for device in new_devices:
# See if the device scanner knows the name
# else defaults to unknown device
name = self.device_scanner.get_device_name(device) or \
DEVICE_DEFAULT_NAME
track = 0
if self.track_new_devices:
self._track_device(device, name)
track = 1
writer.writerow((device, name, track, ""))
if self.track_new_devices:
self._generate_entity_ids(new_devices)
except IOError:
_LOGGER.exception("Error updating %s with %d new devices",
known_dev_path, len(new_devices))
def _track_device(self, device, name):
"""
Add a device to the list of tracked devices.
Does not generate the entity id yet.
"""
default_last_seen = dt_util.utcnow().replace(year=1990)
self.tracked[device] = {
'name': name,
'last_seen': default_last_seen,
'state_attr': {ATTR_FRIENDLY_NAME: name}
}
def _generate_entity_ids(self, need_entity_id):
""" Generate entity ids for a list of devices. """
# Setup entity_ids for the new devices
used_entity_ids = [info['entity_id'] for device, info
in self.tracked.items()
if device not in need_entity_id]
for device in need_entity_id:
name = self.tracked[device]['name']
entity_id = util.ensure_unique_string(
ENTITY_ID_FORMAT.format(util.slugify(name)),
used_entity_ids)
used_entity_ids.append(entity_id)
self.tracked[device]['entity_id'] = entity_id
for key, value in (('name', device.name), ('mac', device.mac),
('picture', device.config_picture),
('track', 'yes' if device.track else 'no'),
(CONF_AWAY_HIDE,
'yes' if device.away_hide else 'no')):
out.write(' {}: {}\n'.format(key, '' if value is None else value))

View File

@ -1,6 +1,6 @@
"""
homeassistant.components.device_tracker.actiontec
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning an Actiontec MI424WR
(Verizon FIOS) router for device presence.
@ -9,13 +9,16 @@ This device tracker needs telnet to be enabled on the router.
Configuration:
To use the Actiontec tracker you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file. If you experience disconnects
you can modify the home_interval variable.
device_tracker:
platform: actiontec
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
# optional:
home_interval: 10
Variables:
@ -30,21 +33,32 @@ The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
home_interval
*Optional
If the home_interval is set then the component will not let a device
be AWAY if it has been HOME in the last home_interval minutes. This is
in addition to the 3 minute wait built into the device_tracker component.
"""
import logging
from datetime import timedelta
from collections import namedtuple
import re
import threading
import telnetlib
import homeassistant.util.dt as dt_util
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.util import Throttle, convert
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
# interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
_LOGGER = logging.getLogger(__name__)
_LEASES_REGEX = re.compile(
@ -54,7 +68,7 @@ _LEASES_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a DD-WRT scanner. """
""" Validates config and returns an Actiontec scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@ -64,59 +78,87 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "ip", "last_update"])
class ActiontecDeviceScanner(object):
""" This class queries a an actiontec router
for connected devices. Adapted from DD-WRT scanner.
"""
This class queries a an actiontec router for connected devices.
Adapted from DD-WRT scanner.
"""
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
self.home_interval = timedelta(minutes=minutes)
self.lock = threading.Lock()
self.last_results = {}
self.last_results = []
# Test the router is accessible
data = self.get_actiontec_data()
self.success_init = data is not None
_LOGGER.info("actiontec scanner initialized")
if self.home_interval:
_LOGGER.info("home_interval set to: %s", self.home_interval)
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return [client['mac'] for client in self.last_results]
return [client.mac for client in self.last_results]
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
if not self.last_results:
return None
for client in self.last_results:
if client['mac'] == device:
return client['ip']
if client.mac == device:
return client.ip
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Actiontec MI424WR router is up
to date. Returns boolean if scanning successful. """
"""
Ensures the information from the Actiontec MI424WR router is up
to date. Returns boolean if scanning successful.
"""
_LOGGER.info("Scanning")
if not self.success_init:
return False
with self.lock:
# _LOGGER.info("Checking ARP")
data = self.get_actiontec_data()
if not data:
exclude_targets = set()
exclude_target_list = []
now = dt_util.now()
if self.home_interval:
for host in self.last_results:
if host.last_update + self.home_interval > now:
exclude_targets.add(host)
if len(exclude_targets) > 0:
exclude_target_list = [t.ip for t in exclude_targets]
actiontec_data = self.get_actiontec_data()
if not actiontec_data:
return False
active_clients = [client for client in data.values()]
self.last_results = active_clients
self.last_results = []
for client in exclude_target_list:
if client in actiontec_data:
actiontec_data.pop(client)
for name, data in actiontec_data.items():
device = Device(data['mac'], name, now)
self.last_results.append(device)
self.last_results.extend(exclude_targets)
_LOGGER.info("actiontec scan successful")
return True
def get_actiontec_data(self):
""" Retrieve data from Actiontec MI424WR and return parsed result. """
""" Retrieve data from Actiontec MI424WR and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'Username: ')

View File

@ -0,0 +1,148 @@
"""
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 configuration.yaml file. You also need to enable Telnet in the
configuration page of your router.
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+')
# 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

@ -9,7 +9,7 @@ This device tracker needs telnet to be enabled on the router.
Configuration:
To use the ASUSWRT tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: asuswrt
@ -63,7 +63,7 @@ _IP_NEIGH_REGEX = re.compile(
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a DD-WRT scanner. """
""" Validates config and returns an ASUS-WRT scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
@ -75,7 +75,8 @@ def get_scanner(hass, config):
class AsusWrtDeviceScanner(object):
""" This class queries a router running ASUSWRT firmware
"""
This class queries a router running ASUSWRT firmware
for connected devices. Adapted from DD-WRT scanner.
"""
@ -93,8 +94,9 @@ class AsusWrtDeviceScanner(object):
self.success_init = data is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info()
return [client['mac'] for client in self.last_results]
@ -110,8 +112,10 @@ class AsusWrtDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the ASUSWRT router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the ASUSWRT router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@ -129,7 +133,7 @@ class AsusWrtDeviceScanner(object):
return True
def get_asuswrt_data(self):
""" Retrieve data from ASUSWRT and return parsed result. """
""" Retrieve data from ASUSWRT and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'login: ')

View File

@ -1,14 +1,13 @@
"""
homeassistant.components.device_tracker.ddwrt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a DD-WRT router for device
presence.
Configuration:
To use the DD-WRT tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: ddwrt
@ -64,7 +63,8 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class DdWrtDeviceScanner(object):
""" This class queries a wireless router running DD-WRT firmware
"""
This class queries a wireless router running DD-WRT firmware
for connected devices. Adapted from Tomato scanner.
"""
@ -85,8 +85,9 @@ class DdWrtDeviceScanner(object):
self.success_init = data is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@ -124,8 +125,10 @@ class DdWrtDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the DD-WRT router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the DD-WRT router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@ -163,7 +166,7 @@ class DdWrtDeviceScanner(object):
return False
def get_ddwrt_data(self, url):
""" Retrieve data from DD-WRT and return parsed result. """
""" Retrieve data from DD-WRT and return parsed result. """
try:
response = requests.get(
url,

View File

@ -1,18 +1,16 @@
"""
homeassistant.components.device_tracker.luci
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
It's required that the luci RPC package is installed on the OpenWRT router:
# opkg install luci-mod-rpc
Configuration:
To use the Luci tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: luci
@ -66,7 +64,8 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes
class LuciDeviceScanner(object):
""" This class queries a wireless router running OpenWrt firmware
"""
This class queries a wireless router running OpenWrt firmware
for connected devices. Adapted from Tomato scanner.
# opkg install luci-mod-rpc
@ -95,8 +94,9 @@ class LuciDeviceScanner(object):
self.success_init = self.token is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@ -124,8 +124,10 @@ class LuciDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
@ -179,6 +181,6 @@ def _req_json_rpc(url, method, *args, **kwargs):
def _get_token(host, username, password):
""" Get authentication token for the given host+username+password """
""" Get authentication token for the given host+username+password. """
url = 'http://{}/cgi-bin/luci/rpc/auth'.format(host)
return _req_json_rpc(url, 'login', username, password)

View File

@ -0,0 +1,48 @@
"""
homeassistant.components.device_tracker.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MQTT platform for the device tracker.
device_tracker:
platform: mqtt
qos: 1
devices:
paulus_oneplus: /location/paulus
annetherese_n4: /location/annetherese
"""
import logging
from homeassistant import util
import homeassistant.components.mqtt as mqtt
DEPENDENCIES = ['mqtt']
CONF_QOS = 'qos'
CONF_DEVICES = 'devices'
DEFAULT_QOS = 0
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see):
""" Set up a MQTT tracker. """
devices = config.get(CONF_DEVICES)
qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS)
if not isinstance(devices, dict):
_LOGGER.error('Expected %s to be a dict, found %s', CONF_DEVICES,
devices)
return False
dev_id_lookup = {}
def device_tracker_message_received(topic, payload, qos):
""" MQTT message received. """
see(dev_id=dev_id_lookup[topic], location_name=payload)
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
mqtt.subscribe(hass, topic, device_tracker_message_received, qos)
return True

View File

@ -1,14 +1,13 @@
"""
homeassistant.components.device_tracker.netgear
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Netgear router for device
presence.
Configuration:
To use the Netgear tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: netgear
@ -42,7 +41,7 @@ from homeassistant.components.device_tracker import DOMAIN
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pynetgear>=0.3']
REQUIREMENTS = ['pynetgear==0.3']
def get_scanner(hass, config):
@ -71,7 +70,6 @@ class NetgearDeviceScanner(object):
self.lock = threading.Lock()
if host is None:
print("BIER")
self._api = pynetgear.Netgear()
elif username is None:
self._api = pynetgear.Netgear(password, host)
@ -90,8 +88,9 @@ class NetgearDeviceScanner(object):
_LOGGER.error("Failed to Login")
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return (device.mac for device in self.last_results)
@ -106,8 +105,10 @@ class NetgearDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Retrieves latest information from the Netgear router.
Returns boolean if scanning successful. """
"""
Retrieves latest information from the Netgear router.
Returns boolean if scanning successful.
"""
if not self.success_init:
return

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.device_tracker.nmap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a network with nmap.
Configuration:
To use the nmap tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: nmap_tracker
@ -19,6 +18,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,9 +30,6 @@ from collections import namedtuple
import subprocess
import re
from libnmap.process import NmapProcess
from libnmap.parser import NmapParser, NmapParserException
import homeassistant.util.dt as dt_util
from homeassistant.const import CONF_HOSTS
from homeassistant.helpers import validate_config
@ -43,7 +44,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.3']
REQUIREMENTS = ['python-nmap==0.4.1']
def get_scanner(hass, config):
@ -68,11 +69,11 @@ 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):
""" This class scans for devices using nmap """
""" This class scans for devices using nmap. """
def __init__(self, config):
self.last_results = []
@ -81,13 +82,13 @@ 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):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@ -104,42 +105,17 @@ 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
"""
Scans the network for devices.
Returns boolean if scanning successful.
"""
_LOGGER.info("Scanning")
from nmap import PortScanner, PortScannerError
scanner = PortScanner()
options = "-F --host-timeout 5"
exclude_targets = set()
if self.home_interval:
@ -151,14 +127,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

@ -0,0 +1,160 @@
"""
homeassistant.components.device_tracker.thomson
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a THOMSON router for device
presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the THOMSON tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: thomson
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
"""
import logging
from datetime import timedelta
import re
import threading
import telnetlib
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
_DEVICES_REGEX = re.compile(
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
r'(?P<status>([^\s]+))\s+' +
r'(?P<type>([^\s]+))\s+' +
r'(?P<intf>([^\s]+))\s+' +
r'(?P<hwintf>([^\s]+))\s+' +
r'(?P<host>([^\s]+))')
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a THOMSON scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = ThomsonDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class ThomsonDeviceScanner(object):
"""
This class queries a router running THOMSON firmware
for connected devices. Adapted from ASUSWRT scanner.
"""
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {}
# Test the router is accessible
data = self.get_thomson_data()
self.success_init = data is not None
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
self._update_info()
return [client['mac'] for client in self.last_results]
def get_device_name(self, device):
""" Returns the name of the given device
or None if we don't know. """
if not self.last_results:
return None
for client in self.last_results:
if client['mac'] == device:
return client['host']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the THOMSON router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Checking ARP")
data = self.get_thomson_data()
if not data:
return False
# flag C stands for CONNECTED
active_clients = [client for client in data.values() if
client['status'].find('C') != -1]
self.last_results = active_clients
return True
def get_thomson_data(self):
""" Retrieve data from THOMSON and return parsed result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'Username : ')
telnet.write((self.username + '\r\n').encode('ascii'))
telnet.read_until(b'Password : ')
telnet.write((self.password + '\r\n').encode('ascii'))
telnet.read_until(b'=>')
telnet.write(('hostmgr list\r\n').encode('ascii'))
devices_result = telnet.read_until(b'=>').split(b'\r\n')
telnet.write('exit\r\n'.encode('ascii'))
except EOFError:
_LOGGER.exception("Unexpected response from router")
return
except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router," +
" is telnet enabled?")
return
devices = {}
for device in devices_result:
match = _DEVICES_REGEX.search(device.decode('utf-8'))
if match:
devices[match.group('ip')] = {
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
'host': match.group('host'),
'status': match.group('status')
}
return devices

View File

@ -1,14 +1,13 @@
"""
homeassistant.components.device_tracker.tomato
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Tomato router for device
presence.
Configuration:
To use the Tomato tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: tomato

View File

@ -1,14 +1,13 @@
"""
homeassistant.components.device_tracker.tplink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a TP-Link router for device
presence.
Configuration:
To use the TP-Link tracker you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
device_tracker:
platform: tplink
@ -29,7 +28,6 @@ The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
"""
import base64
import logging
@ -65,7 +63,8 @@ def get_scanner(hass, config):
class TplinkDeviceScanner(object):
""" This class queries a wireless router running TP-Link firmware
"""
This class queries a wireless router running TP-Link firmware
for connected devices.
"""
@ -85,8 +84,9 @@ class TplinkDeviceScanner(object):
self.success_init = self._update_info()
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
@ -94,15 +94,18 @@ class TplinkDeviceScanner(object):
# pylint: disable=no-self-use
def get_device_name(self, device):
""" The TP-Link firmware doesn't save the name of the wireless
device. """
"""
The TP-Link firmware doesn't save the name of the wireless device.
"""
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
@ -122,28 +125,33 @@ class TplinkDeviceScanner(object):
class Tplink2DeviceScanner(TplinkDeviceScanner):
""" This class queries a wireless router running newer version of TP-Link
"""
This class queries a wireless router running newer version of TP-Link
firmware for connected devices.
"""
def scan_devices(self):
""" Scans for new devices and return a
list containing found device ids. """
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return self.last_results.keys()
# pylint: disable=no-self-use
def get_device_name(self, device):
""" The TP-Link firmware doesn't save the name of the wireless
device. """
"""
The TP-Link firmware doesn't save the name of the wireless device.
"""
return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful. """
"""
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")

View File

@ -19,22 +19,22 @@ from homeassistant.const import (
DOMAIN = "discovery"
DEPENDENCIES = []
REQUIREMENTS = ['netdisco>=0.3']
REQUIREMENTS = ['netdisco==0.4']
SCAN_INTERVAL = 300 # seconds
# Next 3 lines for now a mirror from netdisco.const
# Should setup a mapping netdisco.const -> own constants
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos'
SERVICE_HANDLERS = {
SERVICE_WEMO: "switch",
SERVICE_CAST: "media_player",
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
}
@ -79,13 +79,6 @@ def setup(hass, config):
if not component:
return
# Hack - fix when device_tracker supports discovery
if service == SERVICE_NETGEAR:
bootstrap.setup_component(hass, component, {
'device_tracker': {'platform': 'netgear'}
})
return
# This component cannot be setup.
if not bootstrap.setup_component(hass, component, config):
return

View File

@ -5,24 +5,47 @@
<title>Home Assistant</title>
<link rel='manifest' href='/static/manifest.json' />
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width,
user-scalable=no' />
<link rel='shortcut icon' href='/static/favicon.ico' />
<link rel='icon' type='image/png'
href='/static/favicon-192x192.png' sizes='192x192'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/favicon-apple-180x180.png'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width,
user-scalable=no' />
<meta name='theme-color' content='#03a9f4'>
</head>
<body fullbleed>
<h3 id='init' align='center'>Initializing Home Assistant</h3>
<script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />
<home-assistant auth='{{ auth }}'></home-assistant>
<style>
#init {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-webkit-justify-content: center;
-webkit-align-items: center;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
font-family: 'Roboto', 'Noto', sans-serif;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#init div {
line-height: 34px;
margin-bottom: 89px;
}
</style>
</head>
<body fullbleed>
<div id='init'>
<img src='/static/splash.png' height='230' />
<div>Initializing</div>
</div>
<script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />
<home-assistant auth='{{ auth }}'></home-assistant>
</body>
</html>

View File

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

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 1c82a536312e8321716ab7d80a5d17045d20d77f
Subproject commit 9637d5d26516873b8a04a3c62b9596163c822a2d

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

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

View File

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

@ -147,7 +147,7 @@ def _api_history_period(handler, path_match, data):
end_time = start_time + one_day
print("Fetchign", start_time, end_time)
print("Fetching", start_time, end_time)
entity_id = data.get('filter_entity_id')

View File

@ -205,9 +205,14 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.serve_forever()
def register_path(self, method, url, callback, require_auth=True):
""" Registers a path wit the server. """
""" Registers a path with 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)
@ -478,7 +487,7 @@ class ServerSession:
return self._expiry < date_util.utcnow()
class SessionStore:
class SessionStore(object):
""" Responsible for storing and retrieving http sessions """
def __init__(self, enabled=True):
""" Set up the session store """

View File

@ -0,0 +1,79 @@
"""
homeassistant.components.ifttt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component enable you to trigger Maker IFTTT recipes.
Check https://ifttt.com/maker for details.
Configuration:
To use Maker IFTTT you will need to add something like the following to your
config/configuration.yaml.
ifttt:
key: xxxxx-x-xxxxxxxxxxxxx
Variables:
key
*Required
Your api key
"""
import logging
import requests
from homeassistant.helpers import validate_config
_LOGGER = logging.getLogger(__name__)
DOMAIN = "ifttt"
SERVICE_TRIGGER = 'trigger'
ATTR_EVENT = 'event'
ATTR_VALUE1 = 'value1'
ATTR_VALUE2 = 'value2'
ATTR_VALUE3 = 'value3'
DEPENDENCIES = []
REQUIREMENTS = ['pyfttt==0.3']
def trigger(hass, event, value1=None, value2=None, value3=None):
""" Trigger a Maker IFTTT recipe """
data = {
ATTR_EVENT: event,
ATTR_VALUE1: value1,
ATTR_VALUE2: value2,
ATTR_VALUE3: value3,
}
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
def setup(hass, config):
""" Setup the ifttt service component """
if not validate_config(config, {DOMAIN: ['key']}, _LOGGER):
return False
key = config[DOMAIN]['key']
def trigger_service(call):
""" Handle ifttt trigger service calls. """
event = call.data.get(ATTR_EVENT)
value1 = call.data.get(ATTR_VALUE1)
value2 = call.data.get(ATTR_VALUE2)
value3 = call.data.get(ATTR_VALUE3)
if event is None:
return
try:
import pyfttt as pyfttt
pyfttt.send_event(key, event, value1, value2, value3)
except requests.exceptions.RequestException:
_LOGGER.exception("Error communicating with IFTTT")
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service)
return True

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

@ -21,7 +21,7 @@ from homeassistant.const import (
DOMAIN = "isy994"
DEPENDENCIES = []
REQUIREMENTS = ['PyISY>=1.0.5']
REQUIREMENTS = ['PyISY==1.0.5']
DISCOVER_LIGHTS = "isy994.lights"
DISCOVER_SWITCHES = "isy994.switches"
DISCOVER_SENSORS = "isy994.sensors"
@ -156,6 +156,12 @@ class ISYDeviceABC(ToggleEntity):
attr = {ATTR_FRIENDLY_NAME: self.name}
for name, prop in self._attrs.items():
attr[name] = getattr(self, prop)
attr = self._attr_filter(attr)
return attr
def _attr_filter(self, attr):
""" Placeholder for attribute filters. """
# pylint: disable=no-self-use
return attr
@property

View File

@ -14,7 +14,7 @@ from homeassistant.const import (
DOMAIN = "keyboard"
DEPENDENCIES = []
REQUIREMENTS = ['pyuserinput>=0.1.9']
REQUIREMENTS = ['pyuserinput==0.1.9']
def volume_up(hass):

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

@ -16,7 +16,7 @@ from homeassistant.components.light import (
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT,
EFFECT_COLORLOOP)
REQUIREMENTS = ['phue>=0.8']
REQUIREMENTS = ['phue==0.8']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)

View File

@ -38,3 +38,9 @@ class ISYLightDevice(ISYDeviceABC):
_attrs = {ATTR_BRIGHTNESS: 'value'}
_onattrs = [ATTR_BRIGHTNESS]
_states = [STATE_ON, STATE_OFF]
def _attr_filter(self, attr):
""" Filter brightness out of entity while off. """
if ATTR_BRIGHTNESS in attr and not self.is_on:
del attr[ATTR_BRIGHTNESS]
return attr

View File

@ -15,7 +15,7 @@ Support for LimitlessLED bulbs, also known as...
Configuration:
To use limitlessled you will need to add the following to your
config/configuration.yaml.
configuration.yaml file.
light:
platform: limitlessled
@ -24,7 +24,6 @@ light:
group_2_name: Bedroom
group_3_name: Office
group_4_name: Kitchen
"""
import logging
@ -34,7 +33,7 @@ from homeassistant.components.light import (Light, ATTR_BRIGHTNESS,
from homeassistant.util.color import color_RGB_to_xy
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['ledcontroller>=1.0.7']
REQUIREMENTS = ['ledcontroller==1.0.7']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@ -107,7 +106,7 @@ class LimitlessLED(Light):
return self._xy_color
def _xy_to_led_color(self, xy_color):
""" Convert an XY color to the closest LedController color string """
""" Convert an XY color to the closest LedController color string. """
def abs_dist_squared(p_0, p_1):
""" Returns the absolute value of the squared distance """
return abs((p_0[0] - p_1[0])**2 + (p_0[1] - p_1[1])**2)

View File

@ -9,7 +9,7 @@ from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.const import ATTR_FRIENDLY_NAME
import tellcore.constants as tellcore_constants
REQUIREMENTS = ['tellcore-py>=1.0.4']
REQUIREMENTS = ['tellcore-py==1.0.4']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):

View File

@ -1,7 +1,6 @@
"""
homeassistant.components.light.vera
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Vera lights. This component is useful if you wish for switches
connected to your Vera controller to appear as lights in Home Assistant.
All switches will be added as a light unless you exclude them in the config.
@ -9,17 +8,17 @@ All switches will be added as a light unless you exclude them in the config.
Configuration:
To use the Vera lights you will need to add something like the following to
your config/configuration.yaml.
your configuration.yaml file.
light:
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome switch
exclude: true
13:
name: Another switch
platform: vera
vera_controller_url: http://YOUR_VERA_IP:3480/
device_data:
12:
name: My awesome switch
exclude: true
13:
name: Another switch
Variables:
@ -52,8 +51,10 @@ it should be set to "true" if you want this device excluded.
import logging
from requests.exceptions import RequestException
from homeassistant.components.switch.vera import VeraSwitch
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.vera.vera as veraApi
REQUIREMENTS = ['https://github.com/balloob/home-assistant-vera-api/archive/'
'a8f823066ead6c7da6fb5e7abaf16fef62e63364.zip'
'#python-vera==0.1']
_LOGGER = logging.getLogger(__name__)
@ -61,6 +62,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return Vera lights. """
import pyvera as veraApi
base_url = config.get('vera_controller_url')
if not base_url:

View File

@ -9,8 +9,9 @@ from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.wink import WinkToggleDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
'#pywink>=0.1']
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip'
'#python-wink==0.1']
def setup_platform(hass, config, add_devices_callback, discovery_info=None):

View File

@ -25,6 +25,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_CAST: 'cast',
discovery.SERVICE_SONOS: 'sonos',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'

View File

@ -1,10 +1,24 @@
"""
homeassistant.components.media_player.chromecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with Cast devices on the network.
WARNING: This platform is currently not working due to a changed Cast API
WARNING: This platform is currently not working due to a changed Cast API.
Configuration:
To use the chromecast integration you will need to add something like the
following to your configuration.yaml file.
media_player:
platform: chromecast
host: 192.168.1.9
Variables:
host
*Optional
Use only if you don't want to scan for devices.
"""
import logging
@ -19,7 +33,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

@ -1,9 +1,7 @@
"""
homeassistant.components.media_player.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo implementation of the media player.
"""
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)

View File

@ -0,0 +1,191 @@
"""
homeassistant.components.media_player.denon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Denon Network Receivers.
Developed for a Denon DRA-N5, see
http://www.denon.co.uk/chg/product/compactsystems/networkmusicsystems/ceolpiccolo
A few notes:
- As long as this module is active and connected, the receiver does
not seem to accept additional telnet connections.
- Be careful with the volume. 50% or even 100% are very loud.
- To be able to wake up the receiver, activate the "remote" setting
in the receiver's settings.
- Play and pause are supported, toggling is not possible.
- Seeking cannot be implemented as the UI sends absolute positions.
Only seeking via simulated button presses is possible.
Configuration:
To use your Denon you will need to add something like the following to
your config/configuration.yaml:
media_player:
platform: denon
name: Music station
host: 192.168.0.123
Variables:
host
*Required
The ip of the player. Example: 192.168.0.123
name
*Optional
The name of the device.
"""
import telnetlib
import logging
from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
DOMAIN)
from homeassistant.const import (
CONF_HOST, STATE_OFF, STATE_ON, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Denon platform. """
if not config.get(CONF_HOST):
_LOGGER.error(
"Missing required configuration items in %s: %s",
DOMAIN,
CONF_HOST)
return False
add_devices([
DenonDevice(
config.get('name', 'Music station'),
config.get('host'))
])
return True
class DenonDevice(MediaPlayerDevice):
""" Represents a Denon device. """
# pylint: disable=too-many-public-methods
def __init__(self, name, host):
self._name = name
self._host = host
self._telnet = telnetlib.Telnet(self._host)
def query(self, message):
""" Send request and await response from server """
try:
# unspecified command, should be ignored
self._telnet.write("?".encode('UTF-8') + b'\r')
except (EOFError, BrokenPipeError, ConnectionResetError):
self._telnet.open(self._host)
self._telnet.read_very_eager() # skip what is not requested
self._telnet.write(message.encode('ASCII') + b'\r')
# timeout 200ms, defined by protocol
resp = self._telnet.read_until(b'\r', timeout=0.2)\
.decode('UTF-8').strip()
if message == "PW?":
# workaround; PW? sends also SISTATUS
self._telnet.read_until(b'\r', timeout=0.2)
return resp
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
pwstate = self.query('PW?')
if pwstate == "PWSTANDBY":
return STATE_OFF
if pwstate == "PWON":
return STATE_ON
return STATE_UNKNOWN
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return int(self.query('MV?')[len('MV'):]) / 60
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self.query('MU?') == "MUON"
@property
def media_title(self):
""" Current media source. """
return self.query('SI?')[len('SI'):]
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_DENON
def turn_off(self):
""" turn_off media player. """
self.query('PWSTANDBY')
def volume_up(self):
""" volume_up media player. """
self.query('MVUP')
def volume_down(self):
""" volume_down media player. """
self.query('MVDOWN')
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
# 60dB max
self.query('MV' + str(round(volume * 60)).zfill(2))
def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
self.query('MU' + ('ON' if mute else 'OFF'))
def media_play_pause(self):
""" media_play_pause media player. """
raise NotImplementedError()
def media_play(self):
""" media_play media player. """
self.query('NS9A')
def media_pause(self):
""" media_pause media player. """
self.query('NS9B')
def media_next_track(self):
""" Send next track command. """
self.query('NS9D')
def media_previous_track(self):
self.query('NS9E')
def media_seek(self, position):
raise NotImplementedError()
def turn_on(self):
""" turn the media player on. """
self.query('PWON')

View File

@ -0,0 +1,273 @@
"""
homeassistant.components.media_player.itunes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to iTunes-API (https://github.com/maddox/itunes-api)
Configuration:
To use iTunes you will need to add something like the following to
your configuration.yaml file.
media_player:
platform: itunes
name: iTunes
host: http://192.168.1.16
port: 8181
Variables:
name
*Optional
The name of the device.
url
*Required
URL of your running version of iTunes-API. Example: http://192.168.1.50:8181
"""
import logging
from homeassistant.components.media_player import (
MediaPlayerDevice, MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, SUPPORT_SEEK,
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK)
from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED)
import requests
_LOGGER = logging.getLogger(__name__)
SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
class Itunes(object):
""" itunes-api client. """
def __init__(self, host, port):
self.host = host
self.port = port
@property
def _base_url(self):
""" Returns the base url for endpoints. """
return self.host + ":" + str(self.port)
def _request(self, method, path, params=None):
""" Makes the actual request and returns the parsed response. """
url = self._base_url + path
try:
if method == 'GET':
response = requests.get(url)
elif method == "POST":
response = requests.put(url, params)
elif method == "PUT":
response = requests.put(url, params)
elif method == "DELETE":
response = requests.delete(url)
return response.json()
except requests.exceptions.HTTPError:
return {'player_state': 'error'}
except requests.exceptions.RequestException:
return {'player_state': 'offline'}
def _command(self, named_command):
""" Makes a request for a controlling command. """
return self._request('PUT', '/' + named_command)
def now_playing(self):
""" Returns the current state. """
return self._request('GET', '/now_playing')
def set_volume(self, level):
""" Sets the volume and returns the current state, level 0-100. """
return self._request('PUT', '/volume', {'level': level})
def set_muted(self, muted):
""" Mutes and returns the current state, muted True or False. """
return self._request('PUT', '/mute', {'muted': muted})
def play(self):
""" Sets playback to play and returns the current state. """
return self._command('play')
def pause(self):
""" Sets playback to paused and returns the current state. """
return self._command('pause')
def next(self):
""" Skips to the next track and returns the current state. """
return self._command('next')
def previous(self):
""" Skips back and returns the current state. """
return self._command('previous')
def artwork_url(self):
""" Returns a URL of the current track's album art. """
return self._base_url + '/artwork'
# pylint: disable=unused-argument
# pylint: disable=abstract-method
# pylint: disable=too-many-instance-attributes
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the itunes platform. """
add_devices([
ItunesDevice(
config.get('name', 'iTunes'),
config.get('host'),
config.get('port')
)
])
class ItunesDevice(MediaPlayerDevice):
""" Represents a iTunes-API instance. """
# pylint: disable=too-many-public-methods
def __init__(self, name, host, port):
self._name = name
self._host = host
self._port = port
self.client = Itunes(self._host, self._port)
self.current_volume = None
self.muted = None
self.current_title = None
self.current_album = None
self.current_artist = None
self.current_playlist = None
self.content_id = None
self.player_state = None
self.update()
def update_state(self, state_hash):
""" Update all the state properties with the passed in dictionary. """
self.player_state = state_hash.get('player_state', None)
self.current_volume = state_hash.get('volume', 0)
self.muted = state_hash.get('muted', None)
self.current_title = state_hash.get('name', None)
self.current_album = state_hash.get('album', None)
self.current_artist = state_hash.get('artist', None)
self.current_playlist = state_hash.get('playlist', None)
self.content_id = state_hash.get('id', None)
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
if self.player_state == 'offline' or self.player_state is None:
return 'offline'
if self.player_state == 'error':
return 'error'
if self.player_state == 'stopped':
return STATE_IDLE
if self.player_state == 'paused':
return STATE_PAUSED
else:
return STATE_PLAYING
def update(self):
""" Retrieve latest state. """
now_playing = self.client.now_playing()
self.update_state(now_playing)
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self.muted
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return self.current_volume/100.0
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self.content_id
@property
def media_content_type(self):
""" Content type of current playing media. """
return MEDIA_TYPE_MUSIC
@property
def media_image_url(self):
""" Image url of current playing media. """
if self.player_state in (STATE_PLAYING, STATE_IDLE, STATE_PAUSED) and \
self.current_title is not None:
return self.client.artwork_url()
else:
return 'https://cloud.githubusercontent.com/assets/260/9829355' \
'/33fab972-58cf-11e5-8ea2-2ca74bdaae40.png'
@property
def media_title(self):
""" Title of current playing media. """
return self.current_title
@property
def media_artist(self):
""" Artist of current playing media. (Music track only) """
return self.current_artist
@property
def media_album_name(self):
""" Album of current playing media. (Music track only) """
return self.current_album
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_ITUNES
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
response = self.client.set_volume(int(volume * 100))
self.update_state(response)
def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
response = self.client.set_muted(mute)
self.update_state(response)
def media_play(self):
""" media_play media player. """
response = self.client.play()
self.update_state(response)
def media_pause(self):
""" media_pause media player. """
response = self.client.pause()
self.update_state(response)
def media_next_track(self):
""" media_next media player. """
response = self.client.next()
self.update_state(response)
def media_previous_track(self):
""" media_previous media player. """
response = self.client.previous()
self.update_state(response)

View File

@ -6,7 +6,7 @@ Provides an interface to the XBMC/Kodi JSON-RPC API
Configuration:
To use the Kodi you will need to add something like the following to
your config/configuration.yaml.
your configuration.yaml file.
media_player:
platform: kodi
@ -48,7 +48,7 @@ except ImportError:
jsonrpc_requests = None
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['jsonrpc-requests>=0.1']
REQUIREMENTS = ['jsonrpc-requests==0.1']
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
@ -74,7 +74,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def _get_image_url(kodi_url):
""" Helper function that parses the thumbnail URLs used by Kodi """
""" Helper function that parses the thumbnail URLs used by Kodi. """
url_components = urllib.parse.urlparse(kodi_url)
if url_components.scheme == 'image':
@ -107,6 +107,7 @@ class KodiDevice(MediaPlayerDevice):
try:
return self._server.Player.GetActivePlayers()
except jsonrpc_requests.jsonrpc.TransportError:
_LOGGER.exception('Unable to fetch kodi data')
return None
@property
@ -235,7 +236,7 @@ class KodiDevice(MediaPlayerDevice):
self.update_ha_state()
def _set_play_state(self, state):
""" Helper method for play/pause/toggle """
""" Helper method for play/pause/toggle. """
players = self._get_players()
if len(players) != 0:
@ -256,7 +257,7 @@ class KodiDevice(MediaPlayerDevice):
self._set_play_state(False)
def _goto(self, direction):
""" Helper method used for previous/next track """
""" Helper method used for previous/next track. """
players = self._get_players()
if len(players) != 0:

View File

@ -1,19 +1,19 @@
"""
homeassistant.components.media_player.mpd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to interact with a Music Player Daemon.
Configuration:
To use MPD you will need to add something like the following to your
config/configuration.yaml
configuration.yaml file.
media_player:
platform: mpd
server: 127.0.0.1
port: 6600
location: bedroom
password: superSecretPassword123
Variables:
@ -28,6 +28,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
@ -48,7 +52,7 @@ from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-mpd2>=0.5.4']
REQUIREMENTS = ['python-mpd2==0.5.4']
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
@ -61,6 +65,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 +76,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 +88,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 +108,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 +127,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 +213,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

@ -0,0 +1,197 @@
"""
homeassistant.components.media_player.sonos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to Sonos players (via SoCo)
Configuration:
To use SoCo, add something like this to your configuration:
media_player:
platform: sonos
"""
import logging
import datetime
from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_SEEK, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC)
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
REQUIREMENTS = ['SoCo==0.11.1']
_LOGGER = logging.getLogger(__name__)
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Sonos platform. """
import soco
players = soco.discover()
if not players:
_LOGGER.warning('No Sonos speakers found. Disabling: %s', __name__)
return False
add_devices(SonosDevice(hass, p) for p in players)
_LOGGER.info('Added %s Sonos speakers', len(players))
return True
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-public-methods
# pylint: disable=abstract-method
class SonosDevice(MediaPlayerDevice):
""" Represents a Sonos device. """
# pylint: disable=too-many-arguments
def __init__(self, hass, player):
super(SonosDevice, self).__init__()
self._player = player
self.update()
track_utc_time_change(
hass, self.update_sonos,
second=range(0, 60, 5))
@property
def should_poll(self):
return False
def update_sonos(self, now):
""" Updates state, called by track_utc_time_change """
self.update_ha_state(True)
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
if self._status == 'PAUSED_PLAYBACK':
return STATE_PAUSED
if self._status == 'PLAYING':
return STATE_PLAYING
if self._status == 'STOPPED':
return STATE_IDLE
return STATE_UNKNOWN
def update(self):
""" Retrieve latest state. """
self._name = self._player.get_speaker_info()['zone_name'].replace(
' (R)', '').replace(' (L)', '')
self._status = self._player.get_current_transport_info().get(
'current_transport_state')
self._trackinfo = self._player.get_current_track_info()
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return self._player.volume / 100.0
@property
def is_volume_muted(self):
return self._player.mute
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self._trackinfo.get('title', None)
@property
def media_content_type(self):
""" Content type of current playing media. """
return MEDIA_TYPE_MUSIC
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
dur = self._trackinfo.get('duration', '0:00')
# If the speaker is playing from the "line-in" source, getting
# track metadata can return NOT_IMPLEMENTED, which breaks the
# volume logic below
if dur == 'NOT_IMPLEMENTED':
return None
return sum(60 ** x[0] * int(x[1]) for x in
enumerate(reversed(dur.split(':'))))
@property
def media_image_url(self):
""" Image url of current playing media. """
if 'album_art' in self._trackinfo:
return self._trackinfo['album_art']
@property
def media_title(self):
""" Title of current playing media. """
if 'artist' in self._trackinfo and 'title' in self._trackinfo:
return '{artist} - {title}'.format(
artist=self._trackinfo['artist'],
title=self._trackinfo['title']
)
if 'title' in self._status:
return self._trackinfo['title']
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_SONOS
def turn_off(self):
""" turn_off media player. """
self._player.pause()
def volume_up(self):
""" volume_up media player. """
self._player.volume += 1
def volume_down(self):
""" volume_down media player. """
self._player.volume -= 1
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
self._player.volume = str(int(volume * 100))
def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
self._player.mute = mute
def media_play(self):
""" media_play media player. """
self._player.play()
def media_pause(self):
""" media_pause media player. """
self._player.pause()
def media_next_track(self):
""" Send next track command. """
self._player.next()
def media_previous_track(self):
""" Send next track command. """
self._player.previous()
def media_seek(self, position):
""" Send seek command. """
self._player.seek(str(datetime.timedelta(seconds=int(position))))
def turn_on(self):
""" turn the media player on. """
self._player.play()

View File

@ -1,12 +1,12 @@
"""
homeassistant.components.media_player.squeezebox
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the Logitech SqueezeBox API
Configuration:
To use SqueezeBox add something like this to your configuration:
To use SqueezeBox add something something like the following to your
configuration.yaml file.
media_player:
platform: squeezebox
@ -19,19 +19,19 @@ Variables:
host
*Required
The host name or address of the Logitech Media Server
The host name or address of the Logitech Media Server.
port
*Optional
Telnet port to Logitech Media Server, default 9090
Telnet port to Logitech Media Server, default 9090.
usermame
*Optional
Username, if password protection is enabled
Username, if password protection is enabled.
password
*Optional
Password, if password protection is enabled
Password, if password protection is enabled.
"""
import logging
@ -91,7 +91,7 @@ class LogitechMediaServer(object):
self.init_success = True if self.http_port else False
def _get_http_port(self):
""" Get http port from media server, it is used to get cover art """
""" Get http port from media server, it is used to get cover art. """
http_port = None
try:
http_port = self.query('pref', 'httpport', '?')
@ -111,7 +111,7 @@ class LogitechMediaServer(object):
return
def create_players(self):
""" Create a list of SqueezeBoxDevices connected to the LMS """
""" Create a list of SqueezeBoxDevices connected to the LMS. """
players = []
count = self.query('player', 'count', '?')
for index in range(0, int(count)):
@ -121,7 +121,7 @@ class LogitechMediaServer(object):
return players
def query(self, *parameters):
""" Send request and await response from server """
""" Send request and await response from server. """
telnet = telnetlib.Telnet(self.host, self.port)
if self._username and self._password:
telnet.write('login {username} {password}\n'.format(
@ -138,7 +138,7 @@ class LogitechMediaServer(object):
return urllib.parse.unquote(response)
def get_player_status(self, player):
""" Get ithe status of a player """
""" Get ithe status of a player. """
# (title) : Song title
# Requested Information
# a (artist): Artist name 'artist'
@ -195,7 +195,7 @@ class SqueezeBoxDevice(MediaPlayerDevice):
def update(self):
""" Retrieve latest state. """
self._status = self._lms.get_player_status(self._name)
self._status = self._lms.get_player_status(self._id)
@property
def volume_level(self):

View File

@ -1,17 +1,12 @@
"""
homeassistant.components.modbus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Modbus component, using pymodbus (python3 branch)
Configuration:
To use the Forecast sensor you will need to add something like the
following to your config/configuration.yaml
Modbus component, using pymodbus (python3 branch).
Configuration:
To use the Modbus component you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
#Modbus TCP
modbus:
@ -38,8 +33,8 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
DOMAIN = "modbus"
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/python3.zip'
'#pymodbus>=1.2.0']
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/'
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0']
# Type of network
MEDIUM = "type"

View File

@ -46,7 +46,7 @@ The keep alive in seconds for this client. Default is 60.
import logging
import socket
from homeassistant.core import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError
import homeassistant.util as util
from homeassistant.helpers import validate_config
from homeassistant.const import (
@ -66,7 +66,7 @@ SERVICE_PUBLISH = 'publish'
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
DEPENDENCIES = []
REQUIREMENTS = ['paho-mqtt>=1.1']
REQUIREMENTS = ['paho-mqtt==1.1']
CONF_BROKER = 'broker'
CONF_PORT = 'port'
@ -75,21 +75,23 @@ CONF_KEEPALIVE = 'keepalive'
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
ATTR_QOS = 'qos'
ATTR_TOPIC = 'topic'
ATTR_PAYLOAD = 'payload'
ATTR_QOS = 'qos'
def publish(hass, topic, payload):
def publish(hass, topic, payload, qos=None):
""" Send an MQTT message. """
data = {
ATTR_TOPIC: topic,
ATTR_PAYLOAD: payload,
}
if qos is not None:
data[ATTR_QOS] = qos
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
def subscribe(hass, topic, callback, qos=0):
def subscribe(hass, topic, callback, qos=DEFAULT_QOS):
""" Subscribe to a topic. """
def mqtt_topic_subscriber(event):
""" Match subscribed MQTT topic. """
@ -141,9 +143,10 @@ def setup(hass, config):
""" Handle MQTT publish service calls. """
msg_topic = call.data.get(ATTR_TOPIC)
payload = call.data.get(ATTR_PAYLOAD)
qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
if msg_topic is None or payload is None:
return
MQTT_CLIENT.publish(msg_topic, payload)
MQTT_CLIENT.publish(msg_topic, payload, qos)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
@ -177,9 +180,9 @@ class MQTT(object): # pragma: no cover
self._mqttc.on_message = self._mqtt_on_message
self._mqttc.connect(broker, port, keepalive)
def publish(self, topic, payload):
def publish(self, topic, payload, qos):
""" Publish a MQTT message. """
self._mqttc.publish(topic, payload)
self._mqttc.publish(topic, payload, qos)
def unsubscribe(self, topic):
""" Unsubscribe from topic. """

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.notify.file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
File notification service.
Configuration:
To use the File notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: file

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.notify.instapush
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instapush notification service.
Configuration:
To use the Instapush notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: instapush
@ -16,7 +15,7 @@ notify:
event: YOUR_EVENT
tracker: YOUR_TRACKER
VARIABLES:
Variables:
api_key
*Required
@ -50,7 +49,6 @@ curl -X POST \
https://api.instapush.im/v1/post
Details for the API : https://instapush.im/developer/rest
"""
import logging
import json

View File

@ -1,19 +1,18 @@
"""
homeassistant.components.notify.nma
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NMA (Notify My Android) notification service.
Configuration:
To use the NMA notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: nma
api_key: YOUR_API_KEY
VARIABLES:
Variables:
api_key
*Required
@ -21,7 +20,6 @@ Enter the API key for NMA. Go to https://www.notifymyandroid.com and create a
new API key to use with Home Assistant.
Details for the API : https://www.notifymyandroid.com/api.jsp
"""
import logging
import xml.etree.ElementTree as ET

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.notify.pushbullet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PushBullet platform for notify component.
Configuration:
To use the PushBullet notifier you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
notify:
platform: pushbullet
@ -28,11 +27,11 @@ from homeassistant.components.notify import (
from homeassistant.const import CONF_API_KEY
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pushbullet.py>=0.7.1']
REQUIREMENTS = ['pushbullet.py==0.7.1']
def get_service(hass, config):
""" Get the pushbullet notification service. """
""" Get the PushBullet notification service. """
if not validate_config(config,
{DOMAIN: [CONF_API_KEY]},

View File

@ -1,18 +1,17 @@
"""
homeassistant.components.notify.pushover
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pushover platform for notify component.
Configuration:
To use the Pushover notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: pushover
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
platform: pushover
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
Variables:
@ -33,7 +32,6 @@ https://home-assistant.io/images/favicon-192x192.png
user_key
*Required
To retrieve this value log into your account at https://pushover.net
"""
import logging
@ -42,7 +40,7 @@ from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService)
from homeassistant.const import CONF_API_KEY
REQUIREMENTS = ['python-pushover>=0.2']
REQUIREMENTS = ['python-pushover==0.2']
_LOGGER = logging.getLogger(__name__)

View File

@ -6,7 +6,7 @@ Slack platform for notify component.
Configuration:
To use the Slack notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: slack
@ -32,7 +32,7 @@ from homeassistant.components.notify import (
DOMAIN, BaseNotificationService)
from homeassistant.const import CONF_API_KEY
REQUIREMENTS = ['slacker>=0.6.8']
REQUIREMENTS = ['slacker==0.6.8']
_LOGGER = logging.getLogger(__name__)

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.notify.mail
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mail notification service.
Mail (SMTP) notification service.
Configuration:
To use the Mail notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: mail

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.notify.syslog
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Syslog notification service.
Configuration:
To use the Syslog notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: syslog

View File

@ -6,7 +6,7 @@ Jabber (XMPP) notification service.
Configuration:
To use the Jabber notifier you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
notify:
platform: xmpp
@ -45,7 +45,7 @@ from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService)
REQUIREMENTS = ['sleekxmpp>=1.3.1', 'dnspython3>=1.12.0']
REQUIREMENTS = ['sleekxmpp==1.3.1', 'dnspython3==1.12.0']
def get_service(hass, config):

View File

@ -28,7 +28,7 @@ def create_event_listener(schedule, event_listener_data):
service = event_listener_data['service']
(hour, minute, second) = [int(x) for x in
event_listener_data['time'].split(':')]
event_listener_data['time'].split(':', 3)]
return TimeEventListener(schedule, service, hour, minute, second)

View File

@ -6,6 +6,9 @@ supported.
Configuration:
To use the arduino sensor you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: arduino
pins:
@ -46,7 +49,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Arduino platform. """
# Verify that Arduino board is present
# Verify that the Arduino board is present
if arduino.BOARD is None:
_LOGGER.error('A connection has not been made to the Arduino board.')
return False

View File

@ -0,0 +1,150 @@
"""
homeassistant.components.sensor.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The arest sensor will consume an exposed aREST API of a device.
Configuration:
To use the arest sensor you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: arest
resource: http://IP_ADDRESS
monitored_variables:
- name: temperature
unit: '°C'
- name: humidity
unit: '%'
Variables:
resource:
*Required
IP address of the device that is exposing an aREST API.
These are the variables for the monitored_variables array:
name
*Required
The name of the variable you wish to monitor.
unit
*Optional
Defines the units of measurement of the sensor, if any.
Details for the API: http://arest.io
Format of a default JSON response by aREST:
{
"variables":{
"temperature":21,
"humidity":89
},
"id":"device008",
"name":"Bedroom",
"connected":true
}
"""
import logging
from requests import get, exceptions
from datetime import timedelta
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the aREST sensor. """
resource = config.get('resource', None)
try:
response = get(resource, timeout=10)
except exceptions.MissingSchema:
_LOGGER.error("Missing resource or schema in configuration. "
"Add http:// to your URL.")
return False
except exceptions.ConnectionError:
_LOGGER.error("No route to device. "
"Please check the IP address in the configuration file.")
return False
rest = ArestData(resource)
dev = []
for variable in config['monitored_variables']:
if 'unit' not in variable:
variable['unit'] = ' '
if variable['name'] not in response.json()['variables']:
_LOGGER.error('Variable: "%s" does not exist', variable['name'])
else:
dev.append(ArestSensor(rest,
response.json()['name'],
variable['name'],
variable['unit']))
add_devices(dev)
class ArestSensor(Entity):
""" Implements an aREST sensor. """
def __init__(self, rest, location, variable, unit_of_measurement):
self.rest = rest
self._name = '{} {}'.format(location.title(), variable.title())
self._variable = variable
self._state = 'n/a'
self._unit_of_measurement = unit_of_measurement
self.update()
@property
def name(self):
""" The name of the sensor. """
return self._name
@property
def unit_of_measurement(self):
""" Unit the value is expressed in. """
return self._unit_of_measurement
@property
def state(self):
""" Returns the state of the device. """
return self._state
def update(self):
""" Gets the latest data from aREST API and updates the state. """
self.rest.update()
values = self.rest.data
if 'error' in values:
self._state = values['error']
else:
self._state = values[self._variable]
# pylint: disable=too-few-public-methods
class ArestData(object):
""" Class for handling the data retrieval. """
def __init__(self, resource):
self.resource = resource
self.data = dict()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from aREST device. """
try:
response = get(self.resource, timeout=10)
if 'error' in self.data:
del self.data['error']
self.data = response.json()['variables']
except exceptions.ConnectionError:
_LOGGER.error("No route to device. Is device offline?")
self.data['error'] = 'n/a'

View File

@ -1,7 +1,6 @@
"""
homeassistant.components.sensor.bitcoin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bitcoin information service that uses blockchain.info and its online wallet.
Configuration:
@ -12,7 +11,7 @@ check 'Enable Api Access'. You will get an email message from blockchain.info
where you must authorize the API access.
To use the Bitcoin sensor you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
sensor:
platform: bitcoin
@ -71,7 +70,7 @@ from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['blockchain>=1.1.2']
REQUIREMENTS = ['blockchain==1.1.2']
_LOGGER = logging.getLogger(__name__)
OPTION_TYPES = {
'wallet': ['Wallet balance', 'BTC'],

View File

@ -1,9 +1,7 @@
"""
homeassistant.components.sensor.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform that has two fake sensors.
Demo platform that has a couple of fake sensors.
"""
from homeassistant.helpers.entity import Entity
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL

View File

@ -0,0 +1,164 @@
"""
homeassistant.components.sensor.dht
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Adafruit DHT temperature and humidity sensor.
You need a Python3 compatible version of the Adafruit_Python_DHT library
(e.g. https://github.com/mala-zaba/Adafruit_Python_DHT,
also see requirements.txt).
As this requires access to the GPIO, you will need to run home-assistant
as root.
Configuration:
To use the Adafruit DHT sensor you will need to add something like the
following to your configuration.yaml file.
sensor:
platform: dht
sensor: DHT22
pin: 23
monitored_conditions:
- temperature
- humidity
Variables:
sensor
*Required
The sensor type, DHT11, DHT22 or AM2302
pin
*Required
The pin the sensor is connected to, something like
'P8_11' for Beaglebone, '23' for Raspberry Pi
monitored_conditions
*Optional
Conditions to monitor. Available conditions are temperature and humidity.
"""
import logging
from datetime import timedelta
from homeassistant.util import Throttle
from homeassistant.const import TEMP_FAHRENHEIT
from homeassistant.helpers.entity import Entity
# update this requirement to upstream as soon as it supports python3
REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/'
'4101340de8d2457dd194bca1e8d11cbfc237e919.zip'
'#Adafruit_DHT==1.1.0']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'temperature': ['Temperature', ''],
'humidity': ['Humidity', '%']
}
# Return cached results if last scan was less then this time ago
# DHT11 is able to deliver data once per second, DHT22 once every two
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the DHT sensor. """
try:
import Adafruit_DHT
except ImportError:
_LOGGER.exception(
"Unable to import Adafruit_DHT. "
"Did you maybe not install the 'Adafruit_DHT' package?")
return False
SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit
unit = hass.config.temperature_unit
available_sensors = {
"DHT11": Adafruit_DHT.DHT11,
"DHT22": Adafruit_DHT.DHT22,
"AM2302": Adafruit_DHT.AM2302
}
sensor = available_sensors[config['sensor']]
pin = config['pin']
if not sensor or not pin:
_LOGGER.error(
"Config error "
"Please check your settings for DHT, sensor not supported.")
return None
data = DHTClient(Adafruit_DHT, sensor, pin)
dev = []
try:
for variable in config['monitored_conditions']:
if variable not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
else:
dev.append(DHTSensor(data, variable, unit))
except KeyError:
pass
add_devices(dev)
# pylint: disable=too-few-public-methods
class DHTSensor(Entity):
""" Implements an DHT sensor. """
def __init__(self, dht_client, sensor_type, temp_unit):
self.client_name = 'DHT sensor'
self._name = SENSOR_TYPES[sensor_type][0]
self.dht_client = dht_client
self.temp_unit = temp_unit
self.type = sensor_type
self._state = None
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self.update()
@property
def name(self):
return '{} {}'.format(self.client_name, self._name)
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity, if any. """
return self._unit_of_measurement
def update(self):
""" Gets the latest data from the DHT and updates the states. """
self.dht_client.update()
data = self.dht_client.data
if self.type == 'temperature':
self._state = round(data['temperature'], 1)
if self.temp_unit == TEMP_FAHRENHEIT:
self._state = round(data['temperature'] * 1.8 + 32, 1)
elif self.type == 'humidity':
self._state = round(data['humidity'], 1)
class DHTClient(object):
""" Gets the latest data from the DHT sensor. """
def __init__(self, adafruit_dht, sensor, pin):
self.adafruit_dht = adafruit_dht
self.sensor = sensor
self.pin = pin
self.data = dict()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data the DHT sensor. """
humidity, temperature = self.adafruit_dht.read_retry(self.sensor,
self.pin)
if temperature:
self.data['temperature'] = temperature
if humidity:
self.data['humidity'] = humidity

View File

@ -7,7 +7,7 @@ Monitors home energy use as measured by an efergy engage hub using its
Configuration:
To use the efergy sensor you will need to add something like the following
to your config/configuration.yaml
to your configuration.yaml file.
sensor:
platform: efergy
@ -61,7 +61,7 @@ SENSOR_TYPES = {
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the efergy sensor. """
""" Sets up the Efergy sensor. """
app_token = config.get("app_token")
if not app_token:
_LOGGER.error(
@ -118,7 +118,7 @@ class EfergySensor(Entity):
return self._unit_of_measurement
def update(self):
""" Gets the efergy monitor data from the web service """
""" Gets the Efergy monitor data from the web service. """
if self.type == 'instant_readings':
url_string = _RESOURCE + 'getInstant?token=' + self.app_token
response = get(url_string)

View File

@ -1,12 +1,12 @@
"""
homeassistant.components.sensor.forecast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Forecast.io service.
Forecast.io weather service.
Configuration:
To use the Forecast sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: forecast
@ -37,11 +37,9 @@ monitored_conditions
*Required
An array specifying the conditions to monitor.
These are the variables for the monitored_conditions array:
type
monitored_conditions
*Required
The condition you wish to monitor, see the configuration example above for a
Conditions to monitor. See the configuration example above for a
list of all available conditions to monitor.
Details for the API : https://developer.forecast.io/docs/v2
@ -49,7 +47,7 @@ Details for the API : https://developer.forecast.io/docs/v2
import logging
from datetime import timedelta
REQUIREMENTS = ['python-forecastio>=1.3.3']
REQUIREMENTS = ['python-forecastio==1.3.3']
try:
import forecastio
@ -73,7 +71,7 @@ SENSOR_TYPES = {
'humidity': ['Humidity', '%'],
'pressure': ['Pressure', 'mBar'],
'visibility': ['Visibility', 'km'],
'ozone': ['Ozone', ''],
'ozone': ['Ozone', 'DU'],
}
# Return cached results if last scan was less then this time ago

View File

@ -1,7 +1,6 @@
"""
homeassistant.components.sensor.isy994
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for ISY994 sensors.
"""
import logging

View File

@ -4,8 +4,9 @@ homeassistant.components.modbus
Support for Modbus sensors.
Configuration:
To use the Modbus sensors you will need to add something like the following to
your config/configuration.yaml
your configuration.yaml file.
sensor:
platform: modbus
@ -47,7 +48,6 @@ Note:
- Each named register will create an integer sensor.
- Each named bit will create a boolean sensor.
"""
import logging
import homeassistant.components.modbus as modbus
@ -61,7 +61,7 @@ DEPENDENCIES = ['modbus']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Read config and create Modbus devices """
""" Read config and create Modbus devices. """
sensors = []
slave = config.get("slave", None)
if modbus.TYPE == "serial" and not slave:
@ -97,7 +97,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ModbusSensor(Entity):
# pylint: disable=too-many-arguments
""" Represents a Modbus Sensor """
""" Represents a Modbus Sensor. """
def __init__(self, name, slave, register, bit=None, unit=None, coil=False):
self._name = name
@ -113,8 +113,10 @@ class ModbusSensor(Entity):
@property
def should_poll(self):
""" We should poll, because slaves are not allowed to
initiate communication on Modbus networks"""
"""
We should poll, because slaves are not allowed to
initiate communication on Modbus networks.
"""
return True
@property

View File

@ -13,6 +13,7 @@ sensor:
platform: mqtt
name: "MQTT Sensor"
state_topic: "home/bedroom/temperature"
qos: 0
unit_of_measurement: "ºC"
Variables:
@ -25,6 +26,10 @@ state_topic
*Required
The MQTT topic subscribed to receive sensor values.
qos
*Optional
The maximum QoS level of the state topic. Default is 0.
unit_of_measurement
*Optional
Defines the units of measurement of the sensor, if any.
@ -38,6 +43,7 @@ import homeassistant.components.mqtt as mqtt
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "MQTT Sensor"
DEFAULT_QOS = 0
DEPENDENCIES = ['mqtt']
@ -54,16 +60,19 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('qos', DEFAULT_QOS),
config.get('unit_of_measurement'))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttSensor(Entity):
""" Represents a sensor that can be updated using MQTT """
def __init__(self, hass, name, state_topic, unit_of_measurement):
def __init__(self, hass, name, state_topic, qos, unit_of_measurement):
self._state = "-"
self._hass = hass
self._name = name
self._state_topic = state_topic
self._qos = qos
self._unit_of_measurement = unit_of_measurement
def message_received(topic, payload, qos):
@ -71,7 +80,7 @@ class MqttSensor(Entity):
self._state = payload
self.update_ha_state()
mqtt.subscribe(hass, self._state_topic, message_received)
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors sensors.
Configuration:
To use the MySensors sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: mysensors
@ -36,8 +35,9 @@ ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/master.zip'
'#egg=pymysensors-0.1']
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/'
'35b87d880147a34107da0d40cb815d75e6cb4af7.zip'
'#pymysensors==0.2']
def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -6,7 +6,7 @@ OpenWeatherMap (OWM) service.
Configuration:
To use the OpenWeatherMap sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: openweathermap
@ -33,7 +33,7 @@ forecast
Enables the forecast. The default is to display the current conditions.
monitored_conditions
*Optional
*Required
Conditions to monitor. See the configuration example above for a
list of all available conditions to monitor.
@ -48,7 +48,7 @@ from homeassistant.util import Throttle
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pyowm>=2.2.1']
REQUIREMENTS = ['pyowm==2.2.1']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'weather': ['Condition', ''],

View File

@ -4,10 +4,9 @@ homeassistant.components.sensor.rfxtrx
Shows sensor values from RFXtrx sensors.
Configuration:
To use the rfxtrx sensors you will need to add something like the following to
your config/configuration.yaml
Example:
To use the rfxtrx sensors you will need to add something like the following to
your configuration.yaml file.
sensor:
platform: rfxtrx
@ -26,8 +25,8 @@ from collections import OrderedDict
from homeassistant.const import (TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip'
'#RFXtrx>=0.15']
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' +
'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip#RFXtrx==0.15']
DATA_TYPES = OrderedDict([
('Temperature', TEMP_CELCIUS),
@ -102,4 +101,5 @@ class RfxtrxSensor(Entity):
@property
def unit_of_measurement(self):
""" Unit this state is expressed in. """
return self._unit_of_measurement

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
homeassistant.components.sensor.rpi_gpio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a binary state sensor using RPi GPIO.
Note: To use RPi GPIO, Home Assistant must be run as root.
@ -35,11 +35,10 @@ The time in milliseconds for port debouncing. Default is 50ms.
ports
*Required
An array specifying the GPIO ports to use and the name to use in the frontend.
"""
import logging
from homeassistant.helpers.entity import Entity
try:
import RPi.GPIO as GPIO
except ImportError:
@ -53,7 +52,7 @@ DEFAULT_VALUE_HIGH = "HIGH"
DEFAULT_VALUE_LOW = "LOW"
DEFAULT_BOUNCETIME = 50
REQUIREMENTS = ['RPi.GPIO>=0.5.11']
REQUIREMENTS = ['RPi.GPIO==0.5.11']
_LOGGER = logging.getLogger(__name__)
@ -119,12 +118,12 @@ class RPiGPIOSensor(Entity):
@property
def should_poll(self):
""" No polling needed """
""" No polling needed. """
return False
@property
def name(self):
""" The name of the sensor """
""" The name of the sensor. """
return self._name
@property

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.sabnzbd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Monitors SABnzbd NZB client API
Configuration:
To use the SABnzbd sensor you will need to add something like the following to
your config/configuration.yaml
your configuration.yaml file.
sensor:
platform: sabnzbd
@ -27,11 +26,11 @@ Variables:
base_url
*Required
This is the base URL of your SABnzbd instance including the port number if not
running on 80. Example: http://192.168.1.32:8124/
running on 80, e.g. http://192.168.1.32:8124/
name
*Optional
The name to use when displaying this SABnzbd instance
The name to use when displaying this SABnzbd instance.
monitored_variables
*Required
@ -44,17 +43,17 @@ type
The variable you wish to monitor, see the configuration example above for a
list of all available variables.
"""
from homeassistant.util import Throttle
from datetime import timedelta
from homeassistant.helpers.entity import Entity
# pylint: disable=no-name-in-module, import-error
from homeassistant.external.nzbclients.sabnzbd import SabnzbdApi
from homeassistant.external.nzbclients.sabnzbd import SabnzbdApiException
import logging
REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/'
'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip'
'#python-sabnzbd==0.1']
SENSOR_TYPES = {
'current_status': ['Status', ''],
'speed': ['Speed', 'MB/s'],
@ -71,7 +70,9 @@ _THROTTLED_REFRESH = None
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors. """
""" Sets up the SABnzbd sensors. """
from pysabnzbd import SabnzbdApi, SabnzbdApiException
api_key = config.get("api_key")
base_url = config.get("base_url")
name = config.get("name", "SABnzbd")
@ -105,7 +106,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SabnzbdSensor(Entity):
""" A Sabnzbd sensor """
""" Represents an SABnzbd sensor. """
def __init__(self, sensor_type, sabnzb_client, client_name):
self._name = SENSOR_TYPES[sensor_type][0]
@ -132,6 +133,7 @@ class SabnzbdSensor(Entity):
def refresh_sabnzbd_data(self):
""" Calls the throttled SABnzbd refresh method. """
if _THROTTLED_REFRESH is not None:
from pysabnzbd import SabnzbdApiException
try:
_THROTTLED_REFRESH()
except SabnzbdApiException:

View File

@ -1,14 +1,13 @@
"""
homeassistant.components.sensor.swiss_public_transport
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Swiss public transport sensor will give you the next two departure times
from a given location to another one. This sensor is limited to Switzerland.
Configuration:
To use the Swiss public transport sensor you will need to add something like
the following to your config/configuration.yaml
the following to your configuration.yaml file.
sensor:
platform: swiss_public_transport
@ -54,7 +53,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
for location in [config.get('from', None), config.get('to', None)]:
# transport.opendata.ch doesn't play nice with requests.Session
result = get(_RESOURCE + 'locations?query=%s' % location)
result = get(_RESOURCE + 'locations?query=%s' % location,
timeout=10)
journey.append(result.json()['stations'][0]['name'])
except KeyError:
_LOGGER.exception(
@ -116,8 +116,8 @@ class PublicTransportData(object):
'from=' + self.start + '&' +
'to=' + self.destination + '&' +
'fields[]=connections/from/departureTimestamp/&' +
'fields[]=connections/')
'fields[]=connections/',
timeout=10)
connections = response.json()['connections'][:2]
try:

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.systemmonitor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows system monitor values such as: disk, memory and processor use
Shows system monitor values such as: disk, memory, and processor use.
Configuration:
To use the System monitor sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: systemmonitor
@ -66,7 +65,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.const import STATE_ON, STATE_OFF
REQUIREMENTS = ['psutil>=3.0.0']
REQUIREMENTS = ['psutil==3.0.0']
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],

View File

@ -1,8 +1,7 @@
"""
homeassistant.components.sensor.tellstick
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from tellstick sensors.
Shows sensor values from Tellstick sensors.
Possible config keys:
@ -35,7 +34,7 @@ import homeassistant.util as util
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
REQUIREMENTS = ['tellcore-py>=1.0.4']
REQUIREMENTS = ['tellcore-py==1.0.4']
# pylint: disable=unused-argument

View File

@ -4,10 +4,9 @@ homeassistant.components.sensor.temper
Support for getting temperature from TEMPer devices.
Configuration:
To use the temper sensors you will need to add something like the following to
your config/configuration.yaml
Example:
To use the temper sensors you will need to add something like the following to
your configuration.yaml file.
sensor:
platform: temper
@ -18,7 +17,9 @@ from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/master.zip']
REQUIREMENTS = ['https://github.com/rkabadi/temper-python/archive/'
'3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip'
'#temperusb==1.2.3']
# pylint: disable=unused-argument

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.time_date
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Date and Time service.
Configuration:
To use the Date and Time sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: time_date

View File

@ -1,31 +1,30 @@
"""
homeassistant.components.sensor.transmission
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Monitors Transmission BitTorrent client API
Monitors Transmission BitTorrent client API.
Configuration:
To use the Transmission sensor you will need to add something like the
following to your config/configuration.yaml
following to your configuration.yaml file.
sensor:
platform: transmission
name: Transmission
host: 192.168.1.26
port: 9091
username: YOUR_USERNAME
password: YOUR_PASSWORD
monitored_variables:
- type: 'current_status'
- type: 'download_speed'
- type: 'upload_speed'
platform: transmission
name: Transmission
host: 192.168.1.26
port: 9091
username: YOUR_USERNAME
password: YOUR_PASSWORD
monitored_variables:
- 'current_status'
- 'download_speed'
- 'upload_speed'
Variables:
host
*Required
This is the IP address of your Transmission daemon. Example: 192.168.1.32
This is the IP address of your Transmission daemon, e.g. 192.168.1.32
port
*Optional
@ -33,11 +32,11 @@ The port your Transmission daemon uses, defaults to 9091. Example: 8080
username
*Required
Your Transmission username
Your Transmission username.
password
*Required
Your Transmission password
Your Transmission password.
name
*Optional
@ -45,16 +44,9 @@ The name to use when displaying this Transmission instance.
monitored_variables
*Required
An array specifying the variables to monitor.
These are the variables for the monitored_variables array:
type
*Required
The variable you wish to monitor, see the configuration example above for a
list of all available variables.
Variables to monitor. See the configuration example above for a
list of all available variables to monitor.
"""
from homeassistant.util import Throttle
from datetime import timedelta
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
@ -67,7 +59,7 @@ from transmissionrpc.error import TransmissionError
import logging
REQUIREMENTS = ['transmissionrpc>=0.11']
REQUIREMENTS = ['transmissionrpc==0.11']
SENSOR_TYPES = {
'current_status': ['Status', ''],
'download_speed': ['Down Speed', 'MB/s'],
@ -81,7 +73,7 @@ _THROTTLED_REFRESH = None
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors. """
""" Sets up the Transmission sensors. """
host = config.get(CONF_HOST)
username = config.get(CONF_USERNAME, None)
password = config.get(CONF_PASSWORD, None)
@ -110,11 +102,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = []
for variable in config['monitored_variables']:
if variable['type'] not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable['type'])
if variable not in SENSOR_TYPES:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
else:
dev.append(TransmissionSensor(
variable['type'], transmission_api, name))
variable, transmission_api, name))
add_devices(dev)

View File

@ -1,13 +1,12 @@
"""
homeassistant.components.sensor.vera
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Vera sensors.
Configuration:
To use the Vera sensors you will need to add something like the following to
your config/configuration.yaml
your configuration.yaml file.
sensor:
platform: vera
@ -24,8 +23,7 @@ Variables:
vera_controller_url
*Required
This is the base URL of your vera controller including the port number if not
running on 80
Example: http://192.168.1.21:3480/
running on 80, e.g. http://192.168.1.21:3480/
device_data
@ -33,7 +31,7 @@ device_data
This contains an array additional device info for your Vera devices. It is not
required and if not specified all sensors configured in your Vera controller
will be added with default values. You should use the id of your vera device
as the key for the device within device_data
as the key for the device within device_data.
These are the variables for the device_data array:
@ -41,14 +39,12 @@ name
*Optional
This parameter allows you to override the name of your Vera device in the HA
interface, if not specified the value configured for the device in your Vera
will be used
will be used.
exclude
*Optional
This parameter allows you to exclude the specified device from homeassistant,
it should be set to "true" if you want this device excluded
This parameter allows you to exclude the specified device from Home Assistant,
it should be set to "true" if you want this device excluded.
"""
import logging
from requests.exceptions import RequestException
@ -58,8 +54,10 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
TEMP_CELCIUS, TEMP_FAHRENHEIT)
# pylint: disable=no-name-in-module, import-error
import homeassistant.external.vera.vera as veraApi
REQUIREMENTS = ['https://github.com/balloob/home-assistant-vera-api/archive/'
'a8f823066ead6c7da6fb5e7abaf16fef62e63364.zip'
'#python-vera==0.1']
_LOGGER = logging.getLogger(__name__)
@ -67,6 +65,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_devices(hass, config):
""" Find and return Vera Sensors. """
import pyvera as veraApi
base_url = config.get('vera_controller_url')
if not base_url:

View File

@ -8,8 +8,9 @@ import logging
from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/master.zip'
'#pywink>=0.1']
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'c2b700e8ca866159566ecf5e644d9c297f69f257.zip'
'#python-wink==0.1']
def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -31,7 +31,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.components.scheduler import ServiceEventListener
DEPENDENCIES = []
REQUIREMENTS = ['astral>=0.8.1']
REQUIREMENTS = ['astral==0.8.1']
DOMAIN = "sun"
ENTITY_ID = "sun.sun"

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

Some files were not shown because too many files have changed in this diff Show More