mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
merge from dev
This commit is contained in:
commit
964a1f9aef
11
.coveragerc
11
.coveragerc
@ -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
2
.gitignore
vendored
@ -9,6 +9,8 @@ config/custom_components/*
|
||||
!config/custom_components/hello_world.py
|
||||
!config/custom_components/mqtt_example.py
|
||||
|
||||
tests/config/home-assistant.log
|
||||
|
||||
# Hide sublime text stuff
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
1
MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
recursive-exclude tests *
|
39
README.md
39
README.md
@ -1,16 +1,24 @@
|
||||
# Home Assistant [](https://travis-ci.org/balloob/home-assistant) [](https://coveralls.io/r/balloob/home-assistant?branch=master) [](https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control. [Open a demo.](https://home-assistant.io/demo/)
|
||||
[demo]: https://home-assistant.io/demo/
|
||||
|
||||
Check out [the website](https://home-assistant.io) for installation instructions, tutorials and documentation.
|
||||
Home Assistant is a home automation platform running on Python 3. The goal of Home Assistant is to be able to track and control all devices at home and offer a platform for automating control.
|
||||
|
||||
[](https://home-assistant.io/demo/)
|
||||
To get started:
|
||||
```bash
|
||||
python3 -m pip install homeassistant
|
||||
hass --open-ui
|
||||
```
|
||||
|
||||
Check out [the website](https://home-assistant.io) for [a demo][demo], installation instructions, tutorials and documentation.
|
||||
|
||||
[][demo]
|
||||
|
||||
Examples of devices it can interface it:
|
||||
|
||||
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
|
||||
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Efergy](https://efergy.com) plugs, [Edimax](http://www.edimax.com/) switches, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
|
||||
* [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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 |
@ -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:
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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]}
|
||||
|
@ -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))
|
||||
|
@ -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: ')
|
||||
|
148
homeassistant/components/device_tracker/aruba.py
Normal file
148
homeassistant/components/device_tracker/aruba.py
Normal 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
|
@ -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: ')
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
48
homeassistant/components/device_tracker/mqtt.py
Normal file
48
homeassistant/components/device_tracker/mqtt.py
Normal 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
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
160
homeassistant/components/device_tracker/thomson.py
Normal file
160
homeassistant/components/device_tracker/thomson.py
Normal 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
|
@ -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
|
||||
|
@ -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...")
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
BIN
homeassistant/components/frontend/www_static/splash.png
Normal file
BIN
homeassistant/components/frontend/www_static/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
@ -1,2 +0,0 @@
|
||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = ""
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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 """
|
||||
|
79
homeassistant/components/ifttt.py
Normal file
79
homeassistant/components/ifttt.py
Normal 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
|
@ -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.
|
||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
||||
|
||||
DOMAIN = "isy994"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['PyISY>=1.0.5']
|
||||
REQUIREMENTS = ['PyISY==1.0.5']
|
||||
DISCOVER_LIGHTS = "isy994.lights"
|
||||
DISCOVER_SWITCHES = "isy994.switches"
|
||||
DISCOVER_SENSORS = "isy994.sensors"
|
||||
@ -156,6 +156,12 @@ class ISYDeviceABC(ToggleEntity):
|
||||
attr = {ATTR_FRIENDLY_NAME: self.name}
|
||||
for name, prop in self._attrs.items():
|
||||
attr[name] = getattr(self, prop)
|
||||
attr = self._attr_filter(attr)
|
||||
return attr
|
||||
|
||||
def _attr_filter(self, attr):
|
||||
""" Placeholder for attribute filters. """
|
||||
# pylint: disable=no-self-use
|
||||
return attr
|
||||
|
||||
@property
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.const import (
|
||||
|
||||
DOMAIN = "keyboard"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['pyuserinput>=0.1.9']
|
||||
REQUIREMENTS = ['pyuserinput==0.1.9']
|
||||
|
||||
|
||||
def volume_up(hass):
|
||||
|
@ -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])
|
||||
])
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -25,6 +25,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
DISCOVERY_PLATFORMS = {
|
||||
discovery.SERVICE_CAST: 'cast',
|
||||
discovery.SERVICE_SONOS: 'sonos',
|
||||
}
|
||||
|
||||
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
||||
|
@ -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 | \
|
||||
|
@ -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)
|
||||
|
191
homeassistant/components/media_player/denon.py
Normal file
191
homeassistant/components/media_player/denon.py
Normal 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')
|
273
homeassistant/components/media_player/itunes.py
Normal file
273
homeassistant/components/media_player/itunes.py
Normal 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)
|
@ -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:
|
||||
|
@ -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. """
|
||||
|
197
homeassistant/components/media_player/sonos.py
Normal file
197
homeassistant/components/media_player/sonos.py
Normal 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()
|
@ -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):
|
||||
|
@ -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"
|
||||
|
@ -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. """
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]},
|
||||
|
@ -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__)
|
||||
|
||||
|
||||
|
@ -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__)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
150
homeassistant/components/sensor/arest.py
Normal file
150
homeassistant/components/sensor/arest.py
Normal 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'
|
@ -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'],
|
||||
|
@ -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
|
||||
|
164
homeassistant/components/sensor/dht.py
Normal file
164
homeassistant/components/sensor/dht.py
Normal file
@ -0,0 +1,164 @@
|
||||
"""
|
||||
homeassistant.components.sensor.dht
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Adafruit DHT temperature and humidity sensor.
|
||||
You need a Python3 compatible version of the Adafruit_Python_DHT library
|
||||
(e.g. https://github.com/mala-zaba/Adafruit_Python_DHT,
|
||||
also see requirements.txt).
|
||||
As this requires access to the GPIO, you will need to run home-assistant
|
||||
as root.
|
||||
|
||||
Configuration:
|
||||
|
||||
To use the Adafruit DHT sensor you will need to add something like the
|
||||
following to your 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
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
homeassistant.components.sensor.isy994
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Support for ISY994 sensors.
|
||||
"""
|
||||
import logging
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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', ''],
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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'],
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user