Merge pull request #1502 from balloob/dev

0.15
This commit is contained in:
Paulus Schoutsen 2016-03-12 11:29:30 -08:00
commit 6797365d4f
433 changed files with 6939 additions and 5439 deletions

View File

@ -51,15 +51,15 @@ omit =
homeassistant/components/zwave.py homeassistant/components/zwave.py
homeassistant/components/*/zwave.py homeassistant/components/*/zwave.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/mysensors.py homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py homeassistant/components/*/mysensors.py
homeassistant/components/nest.py homeassistant/components/nest.py
homeassistant/components/*/nest.py homeassistant/components/*/nest.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/rpi_gpio.py homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py homeassistant/components/*/rpi_gpio.py
@ -123,6 +123,7 @@ omit =
homeassistant/components/notify/telegram.py homeassistant/components/notify/telegram.py
homeassistant/components/notify/twitter.py homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py homeassistant/components/notify/xmpp.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/sensor/arest.py homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/cpuspeed.py homeassistant/components/sensor/cpuspeed.py
@ -139,8 +140,8 @@ omit =
homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/speedtest.py homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/temper.py homeassistant/components/sensor/temper.py
@ -150,8 +151,8 @@ omit =
homeassistant/components/sensor/twitch.py homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/worldclock.py homeassistant/components/sensor/worldclock.py
homeassistant/components/switch/arest.py homeassistant/components/switch/arest.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/dlink.py homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py homeassistant/components/switch/mystrom.py
homeassistant/components/switch/orvibo.py homeassistant/components/switch/orvibo.py
@ -162,6 +163,7 @@ omit =
homeassistant/components/thermostat/proliphix.py homeassistant/components/thermostat/proliphix.py
homeassistant/components/thermostat/radiotherm.py homeassistant/components/thermostat/radiotherm.py
[report] [report]
# Regexes for lines to exclude from consideration # Regexes for lines to exclude from consideration
exclude_lines = exclude_lines =

View File

@ -2,6 +2,7 @@
**Related issue (if applicable):** # **Related issue (if applicable):** #
**Example entry for `configuration.yaml` (if applicable):** **Example entry for `configuration.yaml` (if applicable):**
```yaml ```yaml
@ -9,17 +10,17 @@
**Checklist:** **Checklist:**
- [ ] Local tests with `tox` ran successfully. - [ ] Local tests with `tox` run successfully.
- [ ] No CI failures. **Your PR cannot be merged unless CI is green!** - [ ] TravisCI does not fail. **Your PR cannot be merged unless CI is green!**
- [ ] [Fork is up to date][fork] and was rebased on the `dev` branch before creating the PR. - [ ] [Fork is up to date][fork] and was rebased on the `dev` branch before creating the PR.
- [ ] Commits have been [squashed][squash].
- If code communicates with devices: - If code communicates with devices:
- [ ] 3rd party library/libraries for communication is/are added as dependencies via the `REQUIREMENTS` variable ([example][ex-requir]). - [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] 3rd party dependencies are imported inside functions that use them ([example][ex-import]). - [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
- [ ] `requirements_all.txt` is up-to-date, `script/gen_requirements_all.py` ran and the updated file is included in the PR. - [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
- [ ] New files were added to `.coveragerc`. - [ ] New files were added to `.coveragerc`.
- If the code does not depend on external Python module: - If the code does not interact with devices:
- [ ] Tests to verify that the code works are included. - [ ] Tests have been added to verify that the new code works.
- [ ] [Commits will be squashed][squash] when the PR is ready to be merged.
[fork]: http://stackoverflow.com/a/7244456 [fork]: http://stackoverflow.com/a/7244456
[squash]: https://github.com/ginatrapani/todo.txt-android/wiki/Squash-All-Commits-Related-to-a-Single-Issue-into-a-Single-Commit [squash]: https://github.com/ginatrapani/todo.txt-android/wiki/Squash-All-Commits-Related-to-a-Single-Issue-into-a-Single-Commit

View File

@ -71,10 +71,10 @@ When you are done with development and ready to commit your changes, run `build_
To test your code before submission, used the `tox` tool. To test your code before submission, used the `tox` tool.
```shell ```bash
> pip install -U tox > pip install -U tox
> tox > tox
``` ```
This will run unit tests against python 3.4 and 3.5 (if both are available locally), as well as run a set of tests which validate `pep8` and `pylint` style of the code. This will run unit tests against python 3.4 and 3.5 (if both are available locally), as well as run a set of tests which validate `pep8` and `pylint` style of the code.

View File

@ -6,19 +6,17 @@ VOLUME /config
RUN mkdir -p /usr/src/app RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN pip3 install --no-cache-dir colorlog RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker # For the nmap tracker
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools && \ apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave COPY script/build_python_openzwave script/build_python_openzwave
RUN apt-get update && \ RUN script/build_python_openzwave && \
apt-get install -y cython3 libudev-dev && \ mkdir -p /usr/local/share/python-openzwave && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config
pip3 install "cython<0.23" && \
script/build_python_openzwave
COPY requirements_all.txt requirements_all.txt COPY requirements_all.txt requirements_all.txt
RUN pip3 install --no-cache-dir -r requirements_all.txt RUN pip3 install --no-cache-dir -r requirements_all.txt

View File

@ -101,9 +101,10 @@ def track_devices(hass, entity_id, old_state, new_state):
@track_time_change(hour=7, minute=0, second=0) @track_time_change(hour=7, minute=0, second=0)
def wake_up(hass, now): def wake_up(hass, now):
""" """Turn light on in the morning.
Turn it on in the morning (7 AM) if there are people home and
it is not already on. Turn the light on at 7 AM if there are people home and it is not already
on.
""" """
if not TARGET_ID: if not TARGET_ID:
return return
@ -126,8 +127,9 @@ def all_lights_off(hass, entity_id, old_state, new_state):
@service(DOMAIN, SERVICE_FLASH) @service(DOMAIN, SERVICE_FLASH)
def flash_service(hass, call): def flash_service(hass, call):
""" """Service that will toggle the target.
Service that will turn the target off for 10 seconds if on and vice versa.
Set the light to off for 10 seconds if on and vice versa.
""" """
if not TARGET_ID: if not TARGET_ID:
return return

View File

@ -20,7 +20,6 @@ DEPENDENCIES = []
def setup(hass, config): def setup(hass, config):
"""Setup our skeleton component.""" """Setup our skeleton component."""
# States are in the format DOMAIN.OBJECT_ID. # States are in the format DOMAIN.OBJECT_ID.
hass.states.set('hello_world.Hello_World', 'Works!') hass.states.set('hello_world.Hello_World', 'Works!')

View File

@ -0,0 +1 @@
"""Init file for Home Assistant."""

View File

@ -1,4 +1,4 @@
""" Starts home assistant. """ """Starts home assistant."""
from __future__ import print_function from __future__ import print_function
import argparse import argparse
@ -12,21 +12,26 @@ from multiprocessing import Process
import homeassistant.config as config_util import homeassistant.config as config_util
from homeassistant import bootstrap from homeassistant import bootstrap
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, RESTART_EXIT_CODE, __version__) __version__,
EVENT_HOMEASSISTANT_START,
REQUIRED_PYTHON_VER,
RESTART_EXIT_CODE,
)
def validate_python(): def validate_python():
""" Validate we're running the right Python version. """ """Validate we're running the right Python version."""
major, minor = sys.version_info[:2] major, minor = sys.version_info[:2]
req_major, req_minor = REQUIRED_PYTHON_VER
if major < 3 or (major == 3 and minor < 4): if major < req_major or (major == req_major and minor < req_minor):
print("Home Assistant requires atleast Python 3.4") print("Home Assistant requires at least Python {}.{}".format(
req_major, req_minor))
sys.exit(1) sys.exit(1)
def ensure_config_path(config_dir): def ensure_config_path(config_dir):
""" Validates configuration directory. """ """Validate the configuration directory."""
lib_dir = os.path.join(config_dir, 'lib') lib_dir = os.path.join(config_dir, 'lib')
# Test if configuration directory exists # Test if configuration directory exists
@ -54,7 +59,7 @@ def ensure_config_path(config_dir):
def ensure_config_file(config_dir): def ensure_config_file(config_dir):
""" Ensure configuration file exists. """ """Ensure configuration file exists."""
config_path = config_util.ensure_config_exists(config_dir) config_path = config_util.ensure_config_exists(config_dir)
if config_path is None: if config_path is None:
@ -65,7 +70,7 @@ def ensure_config_file(config_dir):
def get_arguments(): def get_arguments():
""" Get parsed passed in arguments. """ """Get parsed passed in arguments."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.") description="Home Assistant: Observe, Control, Automate.")
parser.add_argument('--version', action='version', version=__version__) parser.add_argument('--version', action='version', version=__version__)
@ -130,25 +135,25 @@ def get_arguments():
def daemonize(): def daemonize():
""" Move current process to daemon process """ """Move current process to daemon process."""
# create first fork # Create first fork
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
sys.exit(0) sys.exit(0)
# decouple fork # Decouple fork
os.setsid() os.setsid()
os.umask(0) os.umask(0)
# create second fork # Create second fork
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
sys.exit(0) sys.exit(0)
def check_pid(pid_file): def check_pid(pid_file):
""" Check that HA is not already running """ """Check that HA is not already running."""
# check pid file # Check pid file
try: try:
pid = int(open(pid_file, 'r').readline()) pid = int(open(pid_file, 'r').readline())
except IOError: except IOError:
@ -165,7 +170,7 @@ def check_pid(pid_file):
def write_pid(pid_file): def write_pid(pid_file):
""" Create PID File """ """Create a PID File."""
pid = os.getpid() pid = os.getpid()
try: try:
open(pid_file, 'w').write(str(pid)) open(pid_file, 'w').write(str(pid))
@ -175,7 +180,7 @@ def write_pid(pid_file):
def install_osx(): def install_osx():
""" Setup to run via launchd on OS X """ """Setup to run via launchd on OS X."""
with os.popen('which hass') as inp: with os.popen('which hass') as inp:
hass_path = inp.read().strip() hass_path = inp.read().strip()
@ -207,7 +212,7 @@ def install_osx():
def uninstall_osx(): def uninstall_osx():
""" Unload from launchd on OS X """ """Unload from launchd on OS X."""
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist") path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
os.popen('launchctl unload ' + path) os.popen('launchctl unload ' + path)
@ -215,9 +220,10 @@ def uninstall_osx():
def setup_and_run_hass(config_dir, args, top_process=False): def setup_and_run_hass(config_dir, args, top_process=False):
""" """Setup HASS and run.
Setup HASS and run. Block until stopped. Will assume it is running in a
subprocess unless top_process is set to true. Block until stopped. Will assume it is running in a subprocess unless
top_process is set to true.
""" """
if args.demo_mode: if args.demo_mode:
config = { config = {
@ -237,7 +243,7 @@ def setup_and_run_hass(config_dir, args, top_process=False):
if args.open_ui: if args.open_ui:
def open_browser(event): def open_browser(event):
""" Open the webinterface in a browser. """ """Open the webinterface in a browser."""
if hass.config.api is not None: if hass.config.api is not None:
import webbrowser import webbrowser
webbrowser.open(hass.config.api.base_url) webbrowser.open(hass.config.api.base_url)
@ -253,12 +259,12 @@ def setup_and_run_hass(config_dir, args, top_process=False):
def run_hass_process(hass_proc): def run_hass_process(hass_proc):
""" Runs a child hass process. Returns True if it should be restarted. """ """Run a child hass process. Returns True if it should be restarted."""
requested_stop = threading.Event() requested_stop = threading.Event()
hass_proc.daemon = True hass_proc.daemon = True
def request_stop(*args): def request_stop(*args):
""" request hass stop, *args is for signal handler callback """ """Request hass stop, *args is for signal handler callback."""
requested_stop.set() requested_stop.set()
hass_proc.terminate() hass_proc.terminate()
@ -283,7 +289,7 @@ def run_hass_process(hass_proc):
def main(): def main():
""" Starts Home Assistant. """ """Start Home Assistant."""
validate_python() validate_python()
args = get_arguments() args = get_arguments()
@ -291,7 +297,7 @@ def main():
config_dir = os.path.join(os.getcwd(), args.config) config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir) ensure_config_path(config_dir)
# os x launchd functions # OS X launchd functions
if args.install_osx: if args.install_osx:
install_osx() install_osx()
return 0 return 0
@ -305,7 +311,7 @@ def main():
install_osx() install_osx()
return 0 return 0
# daemon functions # Daemon functions
if args.pid_file: if args.pid_file:
check_pid(args.pid_file) check_pid(args.pid_file)
if args.daemon: if args.daemon:

View File

@ -34,8 +34,7 @@ ERROR_LOG_FILENAME = 'home-assistant.log'
def setup_component(hass, domain, config=None): def setup_component(hass, domain, config=None):
""" Setup a component and all its dependencies. """ """Setup a component and all its dependencies."""
if domain in hass.config.components: if domain in hass.config.components:
return True return True
@ -58,7 +57,7 @@ def setup_component(hass, domain, config=None):
def _handle_requirements(hass, component, name): def _handle_requirements(hass, component, name):
""" Installs requirements for component. """ """Install the requirements for a component."""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'): if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True return True
@ -126,7 +125,7 @@ def _setup_component(hass, domain, config):
def prepare_setup_platform(hass, config, domain, platform_name): def prepare_setup_platform(hass, config, domain, platform_name):
""" Loads a platform and makes sure dependencies are setup. """ """Load a platform and makes sure dependencies are setup."""
_ensure_loader_prepared(hass) _ensure_loader_prepared(hass)
platform_path = PLATFORM_FORMAT.format(domain, platform_name) platform_path = PLATFORM_FORMAT.format(domain, platform_name)
@ -158,7 +157,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
def mount_local_lib_path(config_dir): def mount_local_lib_path(config_dir):
""" Add local library to Python Path """ """Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'lib')) sys.path.insert(0, os.path.join(config_dir, 'lib'))
@ -166,8 +165,7 @@ def mount_local_lib_path(config_dir):
def from_config_dict(config, hass=None, config_dir=None, enable_log=True, def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False, skip_pip=False, verbose=False, daemon=False, skip_pip=False,
log_rotate_days=None): log_rotate_days=None):
""" """Try to configure Home Assistant from a config dict.
Tries to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies. Dynamically loads required components and its dependencies.
""" """
@ -209,7 +207,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
_LOGGER.info('Home Assistant core initialized') _LOGGER.info('Home Assistant core initialized')
# give event decorators access to HASS # Give event decorators access to HASS
event_decorators.HASS = hass event_decorators.HASS = hass
service.HASS = hass service.HASS = hass
@ -222,9 +220,9 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
def from_config_file(config_path, hass=None, verbose=False, daemon=False, def from_config_file(config_path, hass=None, verbose=False, daemon=False,
skip_pip=True, log_rotate_days=None): skip_pip=True, log_rotate_days=None):
""" """Read the configuration file and try to start all the functionality.
Reads the configuration file and tries to start all the required
functionality. Will add functionality to 'hass' parameter if given, Will add functionality to 'hass' parameter if given,
instantiates a new Home Assistant object if 'hass' is not given. instantiates a new Home Assistant object if 'hass' is not given.
""" """
if hass is None: if hass is None:
@ -244,7 +242,7 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None): def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
""" Setup the logging for home assistant. """ """Setup the logging."""
if not daemon: if not daemon:
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
@ -297,7 +295,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
def process_ha_config_upgrade(hass): def process_ha_config_upgrade(hass):
""" Upgrade config if necessary. """ """Upgrade config if necessary."""
version_path = hass.config.path('.HA_VERSION') version_path = hass.config.path('.HA_VERSION')
try: try:
@ -322,11 +320,11 @@ def process_ha_config_upgrade(hass):
def process_ha_core_config(hass, config): def process_ha_core_config(hass, config):
""" Processes the [homeassistant] section from the config. """ """Process the [homeassistant] section from the config."""
hac = hass.config hac = hass.config
def set_time_zone(time_zone_str): def set_time_zone(time_zone_str):
""" Helper method to set time zone in HA. """ """Helper method to set time zone."""
if time_zone_str is None: if time_zone_str is None:
return return
@ -397,6 +395,6 @@ def process_ha_core_config(hass, config):
def _ensure_loader_prepared(hass): def _ensure_loader_prepared(hass):
""" Ensure Home Assistant loader is prepared. """ """Ensure Home Assistant loader is prepared."""
if not loader.PREPARED: if not loader.PREPARED:
loader.prepare(hass) loader.prepare(hass)

View File

@ -1,16 +1,11 @@
""" """
homeassistant.components
~~~~~~~~~~~~~~~~~~~~~~~~
This package contains components that can be plugged into Home Assistant. This package contains components that can be plugged into Home Assistant.
Component design guidelines: Component design guidelines:
- Each component defines a constant DOMAIN that is equal to its filename.
Each component defines a constant DOMAIN that is equal to its filename. - Each component that tracks states should create state entity names in the
format "<DOMAIN>.<OBJECT_ID>".
Each component that tracks states should create state entity names in the - Each component should publish services only under its own domain.
format "<DOMAIN>.<OBJECT_ID>".
Each component should publish services only under its own domain.
""" """
import itertools as it import itertools as it
import logging import logging
@ -26,8 +21,10 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
""" Loads up the module to call the is_on method. """Load up the module to call the is_on method.
If there is no entity id given we will check all. """
If there is no entity id given we will check all.
"""
if entity_id: if entity_id:
group = get_component('group') group = get_component('group')
@ -53,7 +50,7 @@ def is_on(hass, entity_id=None):
def turn_on(hass, entity_id=None, **service_data): def turn_on(hass, entity_id=None, **service_data):
""" Turns specified entity on if possible. """ """Turn specified entity on if possible."""
if entity_id is not None: if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id service_data[ATTR_ENTITY_ID] = entity_id
@ -61,7 +58,7 @@ def turn_on(hass, entity_id=None, **service_data):
def turn_off(hass, entity_id=None, **service_data): def turn_off(hass, entity_id=None, **service_data):
""" Turns specified entity off. """ """Turn specified entity off."""
if entity_id is not None: if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id service_data[ATTR_ENTITY_ID] = entity_id
@ -69,7 +66,7 @@ def turn_off(hass, entity_id=None, **service_data):
def toggle(hass, entity_id=None, **service_data): def toggle(hass, entity_id=None, **service_data):
""" Toggles specified entity. """ """Toggle specified entity."""
if entity_id is not None: if entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id service_data[ATTR_ENTITY_ID] = entity_id
@ -77,10 +74,9 @@ def toggle(hass, entity_id=None, **service_data):
def setup(hass, config): def setup(hass, config):
""" Setup general services related to homeassistant. """ """Setup general services related to Home Assistant."""
def handle_turn_service(service): def handle_turn_service(service):
""" Method to handle calls to homeassistant.turn_on/off. """ """Method to handle calls to homeassistant.turn_on/off."""
entity_ids = extract_entity_ids(hass, service) entity_ids = extract_entity_ids(hass, service)
# Generic turn on/off method requires entity id # Generic turn on/off method requires entity id

View File

@ -1,14 +1,15 @@
""" """
homeassistant.components.alarm_control_panel Component to interface with an alarm control panel.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with a alarm control panel. For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel/
""" """
import logging import logging
import os import os
from homeassistant.components import verisure from homeassistant.components import verisure
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -31,9 +32,6 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_TRIGGER: 'alarm_trigger' SERVICE_ALARM_TRIGGER: 'alarm_trigger'
} }
ATTR_CODE = 'code'
ATTR_CODE_FORMAT = 'code_format'
ATTR_TO_PROPERTY = [ ATTR_TO_PROPERTY = [
ATTR_CODE, ATTR_CODE,
ATTR_CODE_FORMAT ATTR_CODE_FORMAT
@ -41,7 +39,7 @@ ATTR_TO_PROPERTY = [
def setup(hass, config): def setup(hass, config):
""" Track states and offer events for sensors. """ """Track states and offer events for sensors."""
component = EntityComponent( component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS) DISCOVERY_PLATFORMS)
@ -49,7 +47,7 @@ def setup(hass, config):
component.setup(config) component.setup(config)
def alarm_service_handler(service): def alarm_service_handler(service):
""" Maps services to methods on Alarm. """ """Map services to methods on Alarm."""
target_alarms = component.extract_from_service(service) target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data: if ATTR_CODE not in service.data:
@ -75,7 +73,7 @@ def setup(hass, config):
def alarm_disarm(hass, code=None, entity_id=None): def alarm_disarm(hass, code=None, entity_id=None):
""" Send the alarm the command for disarm. """ """Send the alarm the command for disarm."""
data = {} data = {}
if code: if code:
data[ATTR_CODE] = code data[ATTR_CODE] = code
@ -86,7 +84,7 @@ def alarm_disarm(hass, code=None, entity_id=None):
def alarm_arm_home(hass, code=None, entity_id=None): def alarm_arm_home(hass, code=None, entity_id=None):
""" Send the alarm the command for arm home. """ """Send the alarm the command for arm home."""
data = {} data = {}
if code: if code:
data[ATTR_CODE] = code data[ATTR_CODE] = code
@ -97,7 +95,7 @@ def alarm_arm_home(hass, code=None, entity_id=None):
def alarm_arm_away(hass, code=None, entity_id=None): def alarm_arm_away(hass, code=None, entity_id=None):
""" Send the alarm the command for arm away. """ """Send the alarm the command for arm away."""
data = {} data = {}
if code: if code:
data[ATTR_CODE] = code data[ATTR_CODE] = code
@ -108,7 +106,7 @@ def alarm_arm_away(hass, code=None, entity_id=None):
def alarm_trigger(hass, code=None, entity_id=None): def alarm_trigger(hass, code=None, entity_id=None):
""" Send the alarm the command for trigger. """ """Send the alarm the command for trigger."""
data = {} data = {}
if code: if code:
data[ATTR_CODE] = code data[ATTR_CODE] = code
@ -120,32 +118,32 @@ def alarm_trigger(hass, code=None, entity_id=None):
# pylint: disable=no-self-use # pylint: disable=no-self-use
class AlarmControlPanel(Entity): class AlarmControlPanel(Entity):
""" ABC for alarm control devices. """ """An abstract class for alarm control devices."""
@property @property
def code_format(self): def code_format(self):
""" regex for code format or None if no code is required. """ """Regex for code format or None if no code is required."""
return None return None
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
""" Send disarm command. """ """Send disarm command."""
raise NotImplementedError() raise NotImplementedError()
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
""" Send arm home command. """ """Send arm home command."""
raise NotImplementedError() raise NotImplementedError()
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
""" Send arm away command. """ """Send arm away command."""
raise NotImplementedError() raise NotImplementedError()
def alarm_trigger(self, code=None): def alarm_trigger(self, code=None):
""" Send alarm trigger command. """ """Send alarm trigger command."""
raise NotImplementedError() raise NotImplementedError()
@property @property
def state_attributes(self): def state_attributes(self):
""" Return the state attributes. """ """Return the state attributes."""
state_attr = { state_attr = {
ATTR_CODE_FORMAT: self.code_format, ATTR_CODE_FORMAT: self.code_format,
} }

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.alarm_control_panel.alarmdotcom Interfaces with Alarm.com alarm control panels.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure alarm control panel.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/ https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
@ -23,8 +21,7 @@ DEFAULT_NAME = 'Alarm.com'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup an Alarm.com control panel. """ """Setup an Alarm.com control panel."""
username = config.get(CONF_USERNAME) username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
@ -42,9 +39,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method # pylint: disable=abstract-method
class AlarmDotCom(alarm.AlarmControlPanel): class AlarmDotCom(alarm.AlarmControlPanel):
""" Represents a Alarm.com status. """ """Represent an Alarm.com status."""
def __init__(self, hass, name, code, username, password): def __init__(self, hass, name, code, username, password):
"""Initialize the Alarm.com status."""
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
self._alarm = Alarmdotcom(username, password, timeout=10) self._alarm = Alarmdotcom(username, password, timeout=10)
self._hass = hass self._hass = hass
@ -55,22 +53,22 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property @property
def should_poll(self): def should_poll(self):
""" No polling needed. """ """No polling needed."""
return True return True
@property @property
def name(self): def name(self):
""" Returns the name of the device. """ """Return the name of the alarm."""
return self._name return self._name
@property @property
def code_format(self): def code_format(self):
""" One or more characters if code is defined. """ """One or more characters if code is defined."""
return None if self._code is None else '.+' return None if self._code is None else '.+'
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """Return the state of the device."""
if self._alarm.state == 'Disarmed': if self._alarm.state == 'Disarmed':
return STATE_ALARM_DISARMED return STATE_ALARM_DISARMED
elif self._alarm.state == 'Armed Stay': elif self._alarm.state == 'Armed Stay':
@ -81,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_UNKNOWN return STATE_UNKNOWN
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
""" Send disarm command. """ """Send disarm command."""
if not self._validate_code(code, 'arming home'): if not self._validate_code(code, 'arming home'):
return return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
@ -90,7 +88,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
_alarm.disarm() _alarm.disarm()
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
""" Send arm home command. """ """Send arm home command."""
if not self._validate_code(code, 'arming home'): if not self._validate_code(code, 'arming home'):
return return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
@ -99,7 +97,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
_alarm.arm_stay() _alarm.arm_stay()
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
""" Send arm away command. """ """Send arm away command."""
if not self._validate_code(code, 'arming home'): if not self._validate_code(code, 'arming home'):
return return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
@ -108,7 +106,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
_alarm.arm_away() _alarm.arm_away()
def _validate_code(self, code, state): def _validate_code(self, code, state):
""" Validate given code. """ """Validate given code."""
check = self._code is None or code == self._code check = self._code is None or code == self._code
if not check: if not check:
_LOGGER.warning('Wrong code entered for %s', state) _LOGGER.warning('Wrong code entered for %s', state)

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.alarm_control_panel.manual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for manual alarms. Support for manual alarms.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
@ -24,8 +22,7 @@ DEFAULT_TRIGGER_TIME = 120
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the manual alarm platform. """ """Setup the manual alarm platform."""
add_devices([ManualAlarm( add_devices([ManualAlarm(
hass, hass,
config.get('name', DEFAULT_ALARM_NAME), config.get('name', DEFAULT_ALARM_NAME),
@ -47,6 +44,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
""" """
def __init__(self, hass, name, code, pending_time, trigger_time): def __init__(self, hass, name, code, pending_time, trigger_time):
"""Initalize the manual alarm panel."""
self._state = STATE_ALARM_DISARMED self._state = STATE_ALARM_DISARMED
self._hass = hass self._hass = hass
self._name = name self._name = name
@ -57,17 +55,17 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property @property
def should_poll(self): def should_poll(self):
""" No polling needed. """ """No polling needed."""
return False return False
@property @property
def name(self): def name(self):
""" Returns the name of the device. """ """Return the name of the device."""
return self._name return self._name
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """Return the state of the device."""
if self._state in (STATE_ALARM_ARMED_HOME, if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \ STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \ self._pending_time and self._state_ts + self._pending_time > \
@ -85,11 +83,11 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property @property
def code_format(self): def code_format(self):
""" One or more characters. """ """One or more characters."""
return None if self._code is None else '.+' return None if self._code is None else '.+'
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
""" Send disarm command. """ """Send disarm command."""
if not self._validate_code(code, STATE_ALARM_DISARMED): if not self._validate_code(code, STATE_ALARM_DISARMED):
return return
@ -98,7 +96,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self.update_ha_state() self.update_ha_state()
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
""" Send arm home command. """ """Send arm home command."""
if not self._validate_code(code, STATE_ALARM_ARMED_HOME): if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
return return
@ -112,7 +110,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state_ts + self._pending_time) self._state_ts + self._pending_time)
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
""" Send arm away command. """ """Send arm away command."""
if not self._validate_code(code, STATE_ALARM_ARMED_AWAY): if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
return return
@ -126,7 +124,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state_ts + self._pending_time) self._state_ts + self._pending_time)
def alarm_trigger(self, code=None): def alarm_trigger(self, code=None):
""" Send alarm trigger command. No code needed. """ """Send alarm trigger command. No code needed."""
self._state = STATE_ALARM_TRIGGERED self._state = STATE_ALARM_TRIGGERED
self._state_ts = dt_util.utcnow() self._state_ts = dt_util.utcnow()
self.update_ha_state() self.update_ha_state()
@ -141,7 +139,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._state_ts + self._pending_time + self._trigger_time) self._state_ts + self._pending_time + self._trigger_time)
def _validate_code(self, code, state): def _validate_code(self, code, state):
""" Validate given code. """ """Validate given code."""
check = self._code is None or code == self._code check = self._code is None or code == self._code
if not check: if not check:
_LOGGER.warning('Invalid code given for %s', state) _LOGGER.warning('Invalid code given for %s', state)

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.alarm_control_panel.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This platform enables the possibility to control a MQTT alarm. This platform enables the possibility to control a MQTT alarm.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
@ -26,8 +24,7 @@ DEPENDENCIES = ['mqtt']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the MQTT platform. """ """Setup the MQTT platform."""
if config.get('state_topic') is None: if config.get('state_topic') is None:
_LOGGER.error("Missing required variable: state_topic") _LOGGER.error("Missing required variable: state_topic")
return False return False
@ -51,10 +48,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method # pylint: disable=abstract-method
class MqttAlarm(alarm.AlarmControlPanel): class MqttAlarm(alarm.AlarmControlPanel):
""" represents a MQTT alarm status within home assistant. """ """Represent a MQTT alarm status."""
def __init__(self, hass, name, state_topic, command_topic, qos, def __init__(self, hass, name, state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away, code): payload_disarm, payload_arm_home, payload_arm_away, code):
"""Initalize the MQTT alarm panel."""
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
self._hass = hass self._hass = hass
self._name = name self._name = name
@ -67,7 +65,7 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._code = str(code) if code else None self._code = str(code) if code else None
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
""" A new MQTT message has been received. """ """A new MQTT message has been received."""
if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED): STATE_ALARM_TRIGGERED):
@ -80,47 +78,47 @@ class MqttAlarm(alarm.AlarmControlPanel):
@property @property
def should_poll(self): def should_poll(self):
""" No polling needed """ """No polling needed."""
return False return False
@property @property
def name(self): def name(self):
""" Returns the name of the device. """ """Return the name of the device."""
return self._name return self._name
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """Return the state of the device."""
return self._state return self._state
@property @property
def code_format(self): def code_format(self):
""" One or more characters if code is defined """ """One or more characters if code is defined."""
return None if self._code is None else '.+' return None if self._code is None else '.+'
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
""" Send disarm command. """ """Send disarm command."""
if not self._validate_code(code, 'disarming'): if not self._validate_code(code, 'disarming'):
return return
mqtt.publish(self.hass, self._command_topic, mqtt.publish(self.hass, self._command_topic,
self._payload_disarm, self._qos) self._payload_disarm, self._qos)
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
""" Send arm home command. """ """Send arm home command."""
if not self._validate_code(code, 'arming home'): if not self._validate_code(code, 'arming home'):
return return
mqtt.publish(self.hass, self._command_topic, mqtt.publish(self.hass, self._command_topic,
self._payload_arm_home, self._qos) self._payload_arm_home, self._qos)
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
""" Send arm away command. """ """Send arm away command."""
if not self._validate_code(code, 'arming away'): if not self._validate_code(code, 'arming away'):
return return
mqtt.publish(self.hass, self._command_topic, mqtt.publish(self.hass, self._command_topic,
self._payload_arm_away, self._qos) self._payload_arm_away, self._qos)
def _validate_code(self, code, state): def _validate_code(self, code, state):
""" Validate given code. """ """Validate given code."""
check = self._code is None or code == self._code check = self._code is None or code == self._code
if not check: if not check:
_LOGGER.warning('Wrong code entered for %s', state) _LOGGER.warning('Wrong code entered for %s', state)

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.alarm_control_panel.nx584
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for NX584 alarm control panels. Support for NX584 alarm control panels.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
@ -16,12 +14,11 @@ from homeassistant.const import (
STATE_UNKNOWN) STATE_UNKNOWN)
REQUIREMENTS = ['pynx584==0.2'] REQUIREMENTS = ['pynx584==0.2']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup nx584. """ """Setup nx584 platform."""
host = config.get('host', 'localhost:5007') host = config.get('host', 'localhost:5007')
try: try:
@ -32,8 +29,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class NX584Alarm(alarm.AlarmControlPanel): class NX584Alarm(alarm.AlarmControlPanel):
""" NX584-based alarm panel. """ """Represents the NX584-based alarm panel."""
def __init__(self, hass, host, name): def __init__(self, hass, host, name):
"""Initalize the nx584 alarm panel."""
from nx584 import client from nx584 import client
self._hass = hass self._hass = hass
self._host = host self._host = host
@ -46,22 +45,22 @@ class NX584Alarm(alarm.AlarmControlPanel):
@property @property
def should_poll(self): def should_poll(self):
""" Polling needed. """ """Polling needed."""
return True return True
@property @property
def name(self): def name(self):
""" Returns the name of the device. """ """Return the name of the device."""
return self._name return self._name
@property @property
def code_format(self): def code_format(self):
""" Characters if code is defined. """ """The characters if code is defined."""
return '[0-9]{4}([0-9]{2})?' return '[0-9]{4}([0-9]{2})?'
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """Return the state of the device."""
try: try:
part = self._alarm.list_partitions()[0] part = self._alarm.list_partitions()[0]
zones = self._alarm.list_zones() zones = self._alarm.list_zones()
@ -90,17 +89,17 @@ class NX584Alarm(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_AWAY return STATE_ALARM_ARMED_AWAY
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
""" Send disarm command. """ """Send disarm command."""
self._alarm.disarm(code) self._alarm.disarm(code)
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
""" Send arm home command. """ """Send arm home command."""
self._alarm.arm('home') self._alarm.arm('home')
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
""" Send arm away command. """ """Send arm away command."""
self._alarm.arm('auto') self._alarm.arm('auto')
def alarm_trigger(self, code=None): def alarm_trigger(self, code=None):
""" Alarm trigger command. """ """Alarm trigger command."""
raise NotImplementedError() raise NotImplementedError()

View File

@ -1,10 +1,8 @@
""" """
homeassistant.components.alarm_control_panel.verisure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interfaces with Verisure alarm control panel. Interfaces with Verisure alarm control panel.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/verisure/ https://home-assistant.io/components/alarm_control_panel.verisure/
""" """
import logging import logging
@ -19,8 +17,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Verisure platform. """ """Setup the Verisure platform."""
alarms = [] alarms = []
if int(hub.config.get('alarm', '1')): if int(hub.config.get('alarm', '1')):
hub.update_alarms() hub.update_alarms()
@ -33,30 +30,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=abstract-method # pylint: disable=abstract-method
class VerisureAlarm(alarm.AlarmControlPanel): class VerisureAlarm(alarm.AlarmControlPanel):
""" Represents a Verisure alarm status. """ """Represent a Verisure alarm status."""
def __init__(self, device_id): def __init__(self, device_id):
"""Initalize the Verisure alarm panel."""
self._id = device_id self._id = device_id
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
self._digits = int(hub.config.get('code_digits', '4')) self._digits = int(hub.config.get('code_digits', '4'))
@property @property
def name(self): def name(self):
""" Returns the name of the device. """ """Return the name of the device."""
return 'Alarm {}'.format(self._id) return 'Alarm {}'.format(self._id)
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """Return the state of the device."""
return self._state return self._state
@property @property
def code_format(self): def code_format(self):
""" code format as regex """ """The code format as regex."""
return '^\\d{%s}$' % self._digits return '^\\d{%s}$' % self._digits
def update(self): def update(self):
""" Update alarm status """ """Update alarm status."""
hub.update_alarms() hub.update_alarms()
if hub.alarm_status[self._id].status == 'unarmed': if hub.alarm_status[self._id].status == 'unarmed':
@ -71,21 +69,21 @@ class VerisureAlarm(alarm.AlarmControlPanel):
hub.alarm_status[self._id].status) hub.alarm_status[self._id].status)
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
""" Send disarm command. """ """Send disarm command."""
hub.my_pages.alarm.set(code, 'DISARMED') hub.my_pages.alarm.set(code, 'DISARMED')
_LOGGER.info('verisure alarm disarming') _LOGGER.info('verisure alarm disarming')
hub.my_pages.alarm.wait_while_pending() hub.my_pages.alarm.wait_while_pending()
self.update() self.update()
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
""" Send arm home command. """ """Send arm home command."""
hub.my_pages.alarm.set(code, 'ARMED_HOME') hub.my_pages.alarm.set(code, 'ARMED_HOME')
_LOGGER.info('verisure alarm arming home') _LOGGER.info('verisure alarm arming home')
hub.my_pages.alarm.wait_while_pending() hub.my_pages.alarm.wait_while_pending()
self.update() self.update()
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
""" Send arm away command. """ """Send arm away command."""
hub.my_pages.alarm.set(code, 'ARMED_AWAY') hub.my_pages.alarm.set(code, 'ARMED_AWAY')
_LOGGER.info('verisure alarm arming away') _LOGGER.info('verisure alarm arming away')
hub.my_pages.alarm.wait_while_pending() hub.my_pages.alarm.wait_while_pending()

View File

@ -97,21 +97,24 @@ def _handle_alexa(handler, path_match, data):
class SpeechType(enum.Enum): class SpeechType(enum.Enum):
"""Alexa speech types.""" """The Alexa speech types."""
plaintext = "PlainText" plaintext = "PlainText"
ssml = "SSML" ssml = "SSML"
class CardType(enum.Enum): class CardType(enum.Enum):
"""Alexa card types.""" """The Alexa card types."""
simple = "Simple" simple = "Simple"
link_account = "LinkAccount" link_account = "LinkAccount"
class AlexaResponse(object): class AlexaResponse(object):
"""Helps generating the response for Alexa.""" """Help generating the response for Alexa."""
def __init__(self, hass, intent=None): def __init__(self, hass, intent=None):
"""Initialize the response."""
self.hass = hass self.hass = hass
self.speech = None self.speech = None
self.card = None self.card = None
@ -125,7 +128,7 @@ class AlexaResponse(object):
self.variables = {} self.variables = {}
def add_card(self, card_type, title, content): def add_card(self, card_type, title, content):
""" Add a card to the response. """ """Add a card to the response."""
assert self.card is None assert self.card is None
card = { card = {
@ -141,7 +144,7 @@ class AlexaResponse(object):
self.card = card self.card = card
def add_speech(self, speech_type, text): def add_speech(self, speech_type, text):
""" Add speech to the response. """ """Add speech to the response."""
assert self.speech is None assert self.speech is None
key = 'ssml' if speech_type == SpeechType.ssml else 'text' key = 'ssml' if speech_type == SpeechType.ssml else 'text'
@ -163,7 +166,7 @@ class AlexaResponse(object):
} }
def as_dict(self): def as_dict(self):
"""Returns response in an Alexa valid dict.""" """Return response in an Alexa valid dict."""
response = { response = {
'shouldEndSession': self.should_end_session 'shouldEndSession': self.should_end_session
} }

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.apcupsd Support for status output of APCUPSd via its Network Information Server (NIS).
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sets up and provides access to the status output of APCUPSd via its Network
Information Server (NIS).
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/apcupsd/ https://home-assistant.io/components/apcupsd/
@ -34,7 +31,7 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
""" Use config values to set up a function enabling status retrieval. """ """Use config values to set up a function enabling status retrieval."""
global DATA global DATA
host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST) host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST)
@ -54,11 +51,14 @@ def setup(hass, config):
class APCUPSdData(object): class APCUPSdData(object):
"""Stores the data retrieved from APCUPSd.
For each entity to use, acts as the single point responsible for fetching
updates from the server.
""" """
Stores the data retrieved from APCUPSd for each entity to use, acts as the
single point responsible for fetching updates from the server.
"""
def __init__(self, host, port): def __init__(self, host, port):
"""Initialize the data oject."""
from apcaccess import status from apcaccess import status
self._host = host self._host = host
self._port = port self._port = port
@ -68,17 +68,15 @@ class APCUPSdData(object):
@property @property
def status(self): def status(self):
""" Get latest update if throttle allows. Return status. """ """Get latest update if throttle allows. Return status."""
self.update() self.update()
return self._status return self._status
def _get_status(self): def _get_status(self):
""" Get the status from APCUPSd and parse it into a dict. """ """Get the status from APCUPSd and parse it into a dict."""
return self._parse(self._get(host=self._host, port=self._port)) return self._parse(self._get(host=self._host, port=self._port))
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self, **kwargs): def update(self, **kwargs):
""" """Fetch the latest status from APCUPSd."""
Fetch the latest status from APCUPSd and store it in self._status.
"""
self._status = self._get_status() self._status = self._get_status()

View File

@ -34,7 +34,6 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
"""Register the API with the HTTP interface.""" """Register the API with the HTTP interface."""
# /api - for validation purposes # /api - for validation purposes
hass.http.register_path('GET', URL_API, _handle_get_api) hass.http.register_path('GET', URL_API, _handle_get_api)
@ -96,7 +95,7 @@ def setup(hass, config):
def _handle_get_api(handler, path_match, data): def _handle_get_api(handler, path_match, data):
"""Renders the debug interface.""" """Render the debug interface."""
handler.write_json_message("API running.") handler.write_json_message("API running.")
@ -114,7 +113,7 @@ def _handle_get_api_stream(handler, path_match, data):
restrict = restrict.split(',') restrict = restrict.split(',')
def write_message(payload): def write_message(payload):
"""Writes a message to the output.""" """Write a message to the output."""
with write_lock: with write_lock:
msg = "data: {}\n\n".format(payload) msg = "data: {}\n\n".format(payload)
@ -127,7 +126,7 @@ def _handle_get_api_stream(handler, path_match, data):
block.set() block.set()
def forward_events(event): def forward_events(event):
"""Forwards events to the open request.""" """Forward events to the open request."""
nonlocal gracefully_closed nonlocal gracefully_closed
if block.is_set() or event.event_type == EVENT_TIME_CHANGED: if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
@ -171,17 +170,17 @@ def _handle_get_api_stream(handler, path_match, data):
def _handle_get_api_config(handler, path_match, data): def _handle_get_api_config(handler, path_match, data):
"""Returns the Home Assistant configuration.""" """Return the Home Assistant configuration."""
handler.write_json(handler.server.hass.config.as_dict()) handler.write_json(handler.server.hass.config.as_dict())
def _handle_get_api_states(handler, path_match, data): def _handle_get_api_states(handler, path_match, data):
"""Returns a dict containing all entity ids and their state.""" """Return a dict containing all entity ids and their state."""
handler.write_json(handler.server.hass.states.all()) handler.write_json(handler.server.hass.states.all())
def _handle_get_api_states_entity(handler, path_match, data): def _handle_get_api_states_entity(handler, path_match, data):
"""Returns the state of a specific entity.""" """Return the state of a specific entity."""
entity_id = path_match.group('entity_id') entity_id = path_match.group('entity_id')
state = handler.server.hass.states.get(entity_id) state = handler.server.hass.states.get(entity_id)
@ -193,7 +192,7 @@ def _handle_get_api_states_entity(handler, path_match, data):
def _handle_post_state_entity(handler, path_match, data): def _handle_post_state_entity(handler, path_match, data):
"""Handles updating the state of an entity. """Handle updating the state of an entity.
This handles the following paths: This handles the following paths:
/api/states/<entity_id> /api/states/<entity_id>
@ -240,15 +239,14 @@ def _handle_delete_state_entity(handler, path_match, data):
def _handle_get_api_events(handler, path_match, data): def _handle_get_api_events(handler, path_match, data):
"""Handles getting overview of event listeners.""" """Handle getting overview of event listeners."""
handler.write_json(events_json(handler.server.hass)) handler.write_json(events_json(handler.server.hass))
def _handle_api_post_events_event(handler, path_match, event_data): def _handle_api_post_events_event(handler, path_match, event_data):
"""Handles firing of an event. """Handle firing of an event.
This handles the following paths: This handles the following paths: /api/events/<event_type>
/api/events/<event_type>
Events from /api are threated as remote events. Events from /api are threated as remote events.
""" """
@ -276,16 +274,15 @@ def _handle_api_post_events_event(handler, path_match, event_data):
def _handle_get_api_services(handler, path_match, data): def _handle_get_api_services(handler, path_match, data):
"""Handles getting overview of services.""" """Handle getting overview of services."""
handler.write_json(services_json(handler.server.hass)) handler.write_json(services_json(handler.server.hass))
# pylint: disable=invalid-name # pylint: disable=invalid-name
def _handle_post_api_services_domain_service(handler, path_match, data): def _handle_post_api_services_domain_service(handler, path_match, data):
"""Handles calling a service. """Handle calling a service.
This handles the following paths: This handles the following paths: /api/services/<domain>/<service>
/api/services/<domain>/<service>
""" """
domain = path_match.group('domain') domain = path_match.group('domain')
service = path_match.group('service') service = path_match.group('service')
@ -298,7 +295,7 @@ def _handle_post_api_services_domain_service(handler, path_match, data):
# pylint: disable=invalid-name # pylint: disable=invalid-name
def _handle_post_api_event_forward(handler, path_match, data): def _handle_post_api_event_forward(handler, path_match, data):
"""Handles adding an event forwarding target.""" """Handle adding an event forwarding target."""
try: try:
host = data['host'] host = data['host']
api_password = data['api_password'] api_password = data['api_password']
@ -331,7 +328,7 @@ def _handle_post_api_event_forward(handler, path_match, data):
def _handle_delete_api_event_forward(handler, path_match, data): def _handle_delete_api_event_forward(handler, path_match, data):
"""Handles deleting an event forwarding target.""" """Handle deleting an event forwarding target."""
try: try:
host = data['host'] host = data['host']
except KeyError: except KeyError:
@ -354,12 +351,12 @@ def _handle_delete_api_event_forward(handler, path_match, data):
def _handle_get_api_components(handler, path_match, data): def _handle_get_api_components(handler, path_match, data):
"""Returns all the loaded components.""" """Return all the loaded components."""
handler.write_json(handler.server.hass.config.components) handler.write_json(handler.server.hass.config.components)
def _handle_get_api_error_log(handler, path_match, data): def _handle_get_api_error_log(handler, path_match, data):
"""Returns the logged errors for this session.""" """Return the logged errors for this session."""
handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME), handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME),
False) False)

View File

@ -1,8 +1,5 @@
""" """
components.arduino Support for Arduino boards running with the Firmata firmware.
~~~~~~~~~~~~~~~~~~
Arduino component that connects to a directly attached Arduino board which
runs with the Firmata firmware.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arduino/ https://home-assistant.io/components/arduino/
@ -20,8 +17,7 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
""" Setup the Arduino component. """ """Setup the Arduino component."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: ['port']}, {DOMAIN: ['port']},
_LOGGER): _LOGGER):
@ -40,11 +36,11 @@ def setup(hass, config):
return False return False
def stop_arduino(event): def stop_arduino(event):
""" Stop the Arduino service. """ """Stop the Arduino service."""
BOARD.disconnect() BOARD.disconnect()
def start_arduino(event): def start_arduino(event):
""" Start the Arduino service. """ """Start the Arduino service."""
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino)
@ -53,15 +49,16 @@ def setup(hass, config):
class ArduinoBoard(object): class ArduinoBoard(object):
""" Represents an Arduino board. """ """Representation of an Arduino board."""
def __init__(self, port): def __init__(self, port):
"""Initialize the board."""
from PyMata.pymata import PyMata from PyMata.pymata import PyMata
self._port = port self._port = port
self._board = PyMata(self._port, verbose=False) self._board = PyMata(self._port, verbose=False)
def set_mode(self, pin, direction, mode): def set_mode(self, pin, direction, mode):
""" Sets the mode and the direction of a given pin. """ """Set the mode and the direction of a given pin."""
if mode == 'analog' and direction == 'in': if mode == 'analog' and direction == 'in':
self._board.set_pin_mode(pin, self._board.set_pin_mode(pin,
self._board.INPUT, self._board.INPUT,
@ -84,31 +81,31 @@ class ArduinoBoard(object):
self._board.PWM) self._board.PWM)
def get_analog_inputs(self): def get_analog_inputs(self):
""" Get the values from the pins. """ """Get the values from the pins."""
self._board.capability_query() self._board.capability_query()
return self._board.get_analog_response_table() return self._board.get_analog_response_table()
def set_digital_out_high(self, pin): def set_digital_out_high(self, pin):
""" Sets a given digital pin to high. """ """Set a given digital pin to high."""
self._board.digital_write(pin, 1) self._board.digital_write(pin, 1)
def set_digital_out_low(self, pin): def set_digital_out_low(self, pin):
""" Sets a given digital pin to low. """ """Set a given digital pin to low."""
self._board.digital_write(pin, 0) self._board.digital_write(pin, 0)
def get_digital_in(self, pin): def get_digital_in(self, pin):
""" Gets the value from a given digital pin. """ """Get the value from a given digital pin."""
self._board.digital_read(pin) self._board.digital_read(pin)
def get_analog_in(self, pin): def get_analog_in(self, pin):
""" Gets the value from a given analog pin. """ """Get the value from a given analog pin."""
self._board.analog_read(pin) self._board.analog_read(pin)
def get_firmata(self): def get_firmata(self):
""" Return the version of the Firmata firmware. """ """Return the version of the Firmata firmware."""
return self._board.get_firmata_version() return self._board.get_firmata_version()
def disconnect(self): def disconnect(self):
""" Disconnects the board and closes the serial connection. """ """Disconnect the board and close the serial connection."""
self._board.reset() self._board.reset()
self._board.close() self._board.close()

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation Allow to setup simple automation rules via the config file.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to setup simple automation rules via the config file.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/ https://home-assistant.io/components/automation/
@ -12,13 +10,14 @@ from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.const import CONF_PLATFORM from homeassistant.const import CONF_PLATFORM
from homeassistant.components import logbook from homeassistant.components import logbook
from homeassistant.helpers.service import call_from_config from homeassistant.helpers.service import call_from_config
from homeassistant.helpers.service import validate_service_call
DOMAIN = 'automation' DOMAIN = 'automation'
DEPENDENCIES = ['group'] DEPENDENCIES = ['group']
CONF_ALIAS = 'alias' CONF_ALIAS = 'alias'
CONF_SERVICE = 'service'
CONF_CONDITION = 'condition' CONF_CONDITION = 'condition'
CONF_ACTION = 'action' CONF_ACTION = 'action'
@ -35,25 +34,25 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
""" Sets up automation. """ """Setup the automation."""
config_key = DOMAIN config_key = DOMAIN
found = 1 found = 1
while config_key in config: while config_key in config:
# check for one block syntax # Check for one block syntax
if isinstance(config[config_key], dict): if isinstance(config[config_key], dict):
config_block = _migrate_old_config(config[config_key]) config_block = _migrate_old_config(config[config_key])
name = config_block.get(CONF_ALIAS, config_key) name = config_block.get(CONF_ALIAS, config_key)
_setup_automation(hass, config_block, name, config) _setup_automation(hass, config_block, name, config)
# check for multiple block syntax # Check for multiple block syntax
elif isinstance(config[config_key], list): elif isinstance(config[config_key], list):
for list_no, config_block in enumerate(config[config_key]): for list_no, config_block in enumerate(config[config_key]):
name = config_block.get(CONF_ALIAS, name = config_block.get(CONF_ALIAS,
"{}, {}".format(config_key, list_no)) "{}, {}".format(config_key, list_no))
_setup_automation(hass, config_block, name, config) _setup_automation(hass, config_block, name, config)
# any scalar value is incorrect # Any scalar value is incorrect
else: else:
_LOGGER.error('Error in config in section %s.', config_key) _LOGGER.error('Error in config in section %s.', config_key)
@ -64,8 +63,7 @@ def setup(hass, config):
def _setup_automation(hass, config_block, name, config): def _setup_automation(hass, config_block, name, config):
""" Setup one instance of automation """ """Setup one instance of automation."""
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name) action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
if action is None: if action is None:
@ -83,14 +81,14 @@ def _setup_automation(hass, config_block, name, config):
def _get_action(hass, config, name): def _get_action(hass, config, name):
""" Return an action based on a config. """ """Return an action based on a configuration."""
validation_error = validate_service_call(config)
if CONF_SERVICE not in config: if validation_error:
_LOGGER.error('Error setting up %s, no action specified.', name) _LOGGER.error(validation_error)
return None return None
def action(): def action():
""" Action to be executed. """ """Action to be executed."""
_LOGGER.info('Executing %s', name) _LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN) logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
@ -100,7 +98,7 @@ def _get_action(hass, config, name):
def _migrate_old_config(config): def _migrate_old_config(config):
""" Migrate old config to new. """ """Migrate old configuration to new."""
if CONF_PLATFORM not in config: if CONF_PLATFORM not in config:
return config return config
@ -134,8 +132,7 @@ def _migrate_old_config(config):
def _process_if(hass, config, p_config, action): def _process_if(hass, config, p_config, action):
""" Processes if checks. """ """Process if checks."""
cond_type = p_config.get(CONF_CONDITION_TYPE, cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower() DEFAULT_CONDITION_TYPE).lower()
@ -165,12 +162,12 @@ def _process_if(hass, config, p_config, action):
if cond_type == CONDITION_TYPE_AND: if cond_type == CONDITION_TYPE_AND:
def if_action(): def if_action():
""" AND all conditions. """ """AND all conditions."""
if all(check() for check in checks): if all(check() for check in checks):
action() action()
else: else:
def if_action(): def if_action():
""" OR all conditions. """ """OR all conditions."""
if any(check() for check in checks): if any(check() for check in checks):
action() action()
@ -178,7 +175,7 @@ def _process_if(hass, config, p_config, action):
def _process_trigger(hass, config, trigger_configs, name, action): def _process_trigger(hass, config, trigger_configs, name, action):
""" Setup triggers. """ """Setup the triggers."""
if isinstance(trigger_configs, dict): if isinstance(trigger_configs, dict):
trigger_configs = [trigger_configs] trigger_configs = [trigger_configs]
@ -195,7 +192,7 @@ def _process_trigger(hass, config, trigger_configs, name, action):
def _resolve_platform(method, hass, config, platform): def _resolve_platform(method, hass, config, platform):
""" Find automation platform. """ """Find the automation platform."""
if platform is None: if platform is None:
return None return None
platform = prepare_setup_platform(hass, config, DOMAIN, platform) platform = prepare_setup_platform(hass, config, DOMAIN, platform)

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.event Offer event listening automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers event listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger at https://home-assistant.io/components/automation/#event-trigger
@ -15,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for events based on config. """ """Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE) event_type = config.get(CONF_EVENT_TYPE)
if event_type is None: if event_type is None:
@ -25,7 +23,7 @@ def trigger(hass, config, action):
event_data = config.get(CONF_EVENT_DATA) event_data = config.get(CONF_EVENT_DATA)
def handle_event(event): def handle_event(event):
""" Listens for events and calls the action when data matches. """ """Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()): in event_data.items()):
action() action()

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.mqtt Offer MQTT listening automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#mqtt-trigger at https://home-assistant.io/components/automation/#mqtt-trigger
@ -17,7 +15,7 @@ CONF_PAYLOAD = 'payload'
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for state changes based on `config`. """ """Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC) topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD) payload = config.get(CONF_PAYLOAD)
@ -27,7 +25,7 @@ def trigger(hass, config, action):
return False return False
def mqtt_automation_listener(msg_topic, msg_payload, qos): def mqtt_automation_listener(msg_topic, msg_payload, qos):
""" Listens for MQTT messages. """ """Listen for MQTT messages."""
if payload is None or payload == msg_payload: if payload is None or payload == msg_payload:
action() action()

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.numeric_state Offer numeric state listening automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger at https://home-assistant.io/components/automation/#numeric-state-trigger
@ -21,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
def _renderer(hass, value_template, state): def _renderer(hass, value_template, state):
"""Render state value.""" """Render the state value."""
if value_template is None: if value_template is None:
return state.state return state.state
@ -29,7 +27,7 @@ def _renderer(hass, value_template, state):
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for state changes based on `config`. """ """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None: if entity_id is None:
@ -50,8 +48,7 @@ def trigger(hass, config, action):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s): def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """ """Listen for state changes and calls action."""
# Fire action if we go from outside range into range # Fire action if we go from outside range into range
if _in_range(above, below, renderer(to_s)) and \ if _in_range(above, below, renderer(to_s)) and \
(from_s is None or not _in_range(above, below, renderer(from_s))): (from_s is None or not _in_range(above, below, renderer(from_s))):
@ -64,8 +61,7 @@ def trigger(hass, config, action):
def if_action(hass, config): def if_action(hass, config):
""" Wraps action method with state based condition. """ """Wrap action method with state based condition."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None: if entity_id is None:
@ -85,7 +81,7 @@ def if_action(hass, config):
renderer = partial(_renderer, hass, value_template) renderer = partial(_renderer, hass, value_template)
def if_numeric_state(): def if_numeric_state():
""" Test numeric state condition. """ """Test numeric state condition."""
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
return state is not None and _in_range(above, below, renderer(state)) return state is not None and _in_range(above, below, renderer(state))
@ -93,7 +89,7 @@ def if_action(hass, config):
def _in_range(range_start, range_end, value): def _in_range(range_start, range_end, value):
""" Checks if value is inside the range """ """Check if value is inside the range."""
try: try:
value = float(value) value = float(value)
except ValueError: except ValueError:

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.state Offer state listening automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers state listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#state-trigger at https://home-assistant.io/components/automation/#state-trigger
@ -25,7 +23,7 @@ CONF_FOR = "for"
def get_time_config(config): def get_time_config(config):
""" Helper function to extract the time specified in the config """ """Helper function to extract the time specified in the configuration."""
if CONF_FOR not in config: if CONF_FOR not in config:
return None return None
@ -51,7 +49,7 @@ def get_time_config(config):
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for state changes based on `config`. """ """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None: if entity_id is None:
@ -72,17 +70,15 @@ def trigger(hass, config, action):
return None return None
def state_automation_listener(entity, from_s, to_s): def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """ """Listen for state changes and calls action."""
def state_for_listener(now): def state_for_listener(now):
""" Fires on state changes after a delay and calls action. """ """Fire on state changes after a delay and calls action."""
hass.bus.remove_listener( hass.bus.remove_listener(
EVENT_STATE_CHANGED, for_state_listener) EVENT_STATE_CHANGED, for_state_listener)
action() action()
def state_for_cancel_listener(entity, inner_from_s, inner_to_s): def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
""" Fires on state changes and cancels """Fire on changes and cancel for listener if changed."""
for listener if state changed. """
if inner_to_s == to_s: if inner_to_s == to_s:
return return
hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener) hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener)
@ -106,7 +102,7 @@ def trigger(hass, config, action):
def if_action(hass, config): def if_action(hass, config):
""" Wraps action method with state based condition. """ """Wrap action method with state based condition."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
state = config.get(CONF_STATE) state = config.get(CONF_STATE)
@ -123,7 +119,7 @@ def if_action(hass, config):
state = str(state) state = str(state)
def if_state(): def if_state():
""" Test if condition. """ """Test if condition."""
is_state = hass.states.is_state(entity_id, state) is_state = hass.states.is_state(entity_id, state)
return (time_delta is None and is_state or return (time_delta is None and is_state or
time_delta is not None and time_delta is not None and

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.sun Offer sun based automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers sun based automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#sun-trigger at https://home-assistant.io/components/automation/#sun-trigger
@ -29,7 +27,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for events based on config. """ """Listen for events based on configuration."""
event = config.get(CONF_EVENT) event = config.get(CONF_EVENT)
if event is None: if event is None:
@ -55,7 +53,7 @@ def trigger(hass, config, action):
def if_action(hass, config): def if_action(hass, config):
""" Wraps action method with sun based condition. """ """Wrap action method with sun based condition."""
before = config.get(CONF_BEFORE) before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER) after = config.get(CONF_AFTER)
@ -106,8 +104,7 @@ def if_action(hass, config):
return sun.next_setting(hass) + after_offset return sun.next_setting(hass) + after_offset
def time_if(): def time_if():
""" Validate time based if-condition """ """Validate time based if-condition."""
now = dt_util.now() now = dt_util.now()
before = before_func() before = before_func()
after = after_func() after = after_func()
@ -126,6 +123,7 @@ def if_action(hass, config):
def _parse_offset(raw_offset): def _parse_offset(raw_offset):
"""Parse the offset."""
if raw_offset is None: if raw_offset is None:
return timedelta(0) return timedelta(0)

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.template Offer template automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers template automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger at https://home-assistant.io/components/automation/#template-trigger
@ -16,7 +14,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for state changes based on `config`. """ """Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is None: if value_template is None:
@ -27,7 +25,7 @@ def trigger(hass, config, action):
already_triggered = False already_triggered = False
def event_listener(event): def event_listener(event):
""" Listens for state changes and calls action. """ """Listen for state changes and calls action."""
nonlocal already_triggered nonlocal already_triggered
template_result = _check_template(hass, value_template) template_result = _check_template(hass, value_template)
@ -43,8 +41,7 @@ def trigger(hass, config, action):
def if_action(hass, config): def if_action(hass, config):
""" Wraps action method with state based condition. """ """Wrap action method with state based condition."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is None: if value_template is None:
@ -55,7 +52,7 @@ def if_action(hass, config):
def _check_template(hass, value_template): def _check_template(hass, value_template):
""" Checks if result of template is true """ """Check if result of template is true."""
try: try:
value = template.render(hass, value_template, {}) value = template.render(hass, value_template, {})
except TemplateError: except TemplateError:

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.time Offer time listening automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers time listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#time-trigger at https://home-assistant.io/components/automation/#time-trigger
@ -24,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for state changes based on `config`. """ """Listen for state changes based on configuration."""
if CONF_AFTER in config: if CONF_AFTER in config:
after = dt_util.parse_time_str(config[CONF_AFTER]) after = dt_util.parse_time_str(config[CONF_AFTER])
if after is None: if after is None:
@ -42,7 +40,7 @@ def trigger(hass, config, action):
return False return False
def time_automation_listener(now): def time_automation_listener(now):
""" Listens for time changes and calls action. """ """Listen for time changes and calls action."""
action() action()
track_time_change(hass, time_automation_listener, track_time_change(hass, time_automation_listener,
@ -52,7 +50,7 @@ def trigger(hass, config, action):
def if_action(hass, config): def if_action(hass, config):
""" Wraps action method with time based condition. """ """Wrap action method with time based condition."""
before = config.get(CONF_BEFORE) before = config.get(CONF_BEFORE)
after = config.get(CONF_AFTER) after = config.get(CONF_AFTER)
weekday = config.get(CONF_WEEKDAY) weekday = config.get(CONF_WEEKDAY)
@ -76,7 +74,7 @@ def if_action(hass, config):
return None return None
def time_if(): def time_if():
""" Validate time based if-condition """ """Validate time based if-condition."""
now = dt_util.now() now = dt_util.now()
if before is not None and now > now.replace(hour=before.hour, if before is not None and now > now.replace(hour=before.hour,
minute=before.minute): minute=before.minute):
@ -99,7 +97,7 @@ def if_action(hass, config):
def _error_time(value, key): def _error_time(value, key):
""" Helper method to print error. """ """Helper method to print error."""
_LOGGER.error( _LOGGER.error(
"Received invalid value for '%s': %s", key, value) "Received invalid value for '%s': %s", key, value)
if isinstance(value, int): if isinstance(value, int):

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.automation.zone Offer zone automation rules.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers zone automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#zone-trigger at https://home-assistant.io/components/automation/#zone-trigger
@ -22,7 +20,7 @@ DEFAULT_EVENT = EVENT_ENTER
def trigger(hass, config, action): def trigger(hass, config, action):
""" Listen for state changes based on `config`. """ """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE) zone_entity_id = config.get(CONF_ZONE)
@ -35,7 +33,7 @@ def trigger(hass, config, action):
event = config.get(CONF_EVENT, DEFAULT_EVENT) event = config.get(CONF_EVENT, DEFAULT_EVENT)
def zone_automation_listener(entity, from_s, to_s): def zone_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """ """Listen for state changes and calls action."""
if from_s and None in (from_s.attributes.get(ATTR_LATITUDE), if from_s and None in (from_s.attributes.get(ATTR_LATITUDE),
from_s.attributes.get(ATTR_LONGITUDE)) or \ from_s.attributes.get(ATTR_LONGITUDE)) or \
None in (to_s.attributes.get(ATTR_LATITUDE), None in (to_s.attributes.get(ATTR_LATITUDE),
@ -57,7 +55,7 @@ def trigger(hass, config, action):
def if_action(hass, config): def if_action(hass, config):
""" Wraps action method with zone based condition. """ """Wrap action method with zone based condition."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE) zone_entity_id = config.get(CONF_ZONE)
@ -68,14 +66,14 @@ def if_action(hass, config):
return False return False
def if_in_zone(): def if_in_zone():
""" Test if condition. """ """Test if condition."""
return _in_zone(hass, zone_entity_id, hass.states.get(entity_id)) return _in_zone(hass, zone_entity_id, hass.states.get(entity_id))
return if_in_zone return if_in_zone
def _in_zone(hass, zone_entity_id, state): def _in_zone(hass, zone_entity_id, state):
""" Check if state is in zone. """ """Check if state is in zone."""
if not state or None in (state.attributes.get(ATTR_LATITUDE), if not state or None in (state.attributes.get(ATTR_LATITUDE),
state.attributes.get(ATTR_LONGITUDE)): state.attributes.get(ATTR_LONGITUDE)):
return False return False

View File

@ -1,6 +1,5 @@
""" """
Component to interface with binary sensors (sensors which only know two states) Component to interface with binary sensors.
that can be monitored.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor/ https://home-assistant.io/components/binary_sensor/
@ -10,7 +9,7 @@ import logging
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.components import (bloomsky, mysensors, zwave, wink) from homeassistant.components import (bloomsky, mysensors, zwave, wemo, wink)
DOMAIN = 'binary_sensor' DOMAIN = 'binary_sensor'
SCAN_INTERVAL = 30 SCAN_INTERVAL = 30
@ -38,6 +37,7 @@ DISCOVERY_PLATFORMS = {
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
zwave.DISCOVER_BINARY_SENSORS: 'zwave', zwave.DISCOVER_BINARY_SENSORS: 'zwave',
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
wink.DISCOVER_BINARY_SENSORS: 'wink' wink.DISCOVER_BINARY_SENSORS: 'wink'
} }

View File

@ -1,5 +1,5 @@
""" """
Provides a binary sensor to track online status of a UPS. Support for tracking the online status of a UPS.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.apcupsd/ https://home-assistant.io/components/binary_sensor.apcupsd/
@ -17,8 +17,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class OnlineStatus(BinarySensorDevice): class OnlineStatus(BinarySensorDevice):
"""Binary sensor to represent UPS online status.""" """Represent UPS online status."""
def __init__(self, config, data): def __init__(self, config, data):
"""Initialize the APCUPSd device."""
self._config = config self._config = config
self._data = data self._data = data
self._state = None self._state = None
@ -26,17 +28,14 @@ class OnlineStatus(BinarySensorDevice):
@property @property
def name(self): def name(self):
""" The name of the UPS online status sensor. """ """Return the name of the UPS online status sensor."""
return self._config.get("name", DEFAULT_NAME) return self._config.get("name", DEFAULT_NAME)
@property @property
def is_on(self): def is_on(self):
"""True if the UPS is online, else False.""" """Return true if the UPS is online, else false."""
return self._state == apcupsd.VALUE_ONLINE return self._state == apcupsd.VALUE_ONLINE
def update(self): def update(self):
""" """Get the status report from APCUPSd and set this entity's state."""
Get the status report from APCUPSd (or cache) and set this entity's
state.
"""
self._state = self._data.status[apcupsd.KEY_STATUS] self._state = self._data.status[apcupsd.KEY_STATUS]

View File

@ -1,5 +1,5 @@
""" """
The arest sensor will consume an exposed aREST API of a device. Support for exposed aREST RESTful API of a device.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.arest/ https://home-assistant.io/components/binary_sensor.arest/
@ -22,7 +22,7 @@ CONF_PIN = 'pin'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Get the aREST binary sensor.""" """Setup the aREST binary sensor."""
resource = config.get(CONF_RESOURCE) resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN) pin = config.get(CONF_PIN)
@ -53,9 +53,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-instance-attributes, too-many-arguments # pylint: disable=too-many-instance-attributes, too-many-arguments
class ArestBinarySensor(BinarySensorDevice): class ArestBinarySensor(BinarySensorDevice):
"""Implements an aREST binary sensor for a pin.""" """Implement an aREST binary sensor for a pin."""
def __init__(self, arest, resource, name, pin): def __init__(self, arest, resource, name, pin):
"""Initialize the aREST device."""
self.arest = arest self.arest = arest
self._resource = resource self._resource = resource
self._name = name self._name = name
@ -70,30 +71,32 @@ class ArestBinarySensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""The name of the binary sensor.""" """Return the name of the binary sensor."""
return self._name return self._name
@property @property
def is_on(self): def is_on(self):
"""True if the binary sensor is on.""" """Return true if the binary sensor is on."""
return bool(self.arest.data.get('state')) return bool(self.arest.data.get('state'))
def update(self): def update(self):
"""Gets the latest data from aREST API.""" """Get the latest data from aREST API."""
self.arest.update() self.arest.update()
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
class ArestData(object): class ArestData(object):
"""Class for handling the data retrieval for pins.""" """Class for handling the data retrieval for pins."""
def __init__(self, resource, pin): def __init__(self, resource, pin):
"""Initialize the aREST data object."""
self._resource = resource self._resource = resource
self._pin = pin self._pin = pin
self.data = {} self.data = {}
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Gets the latest data from aREST device.""" """Get the latest data from aREST device."""
try: try:
response = requests.get('{}/digital/{}'.format( response = requests.get('{}/digital/{}'.format(
self._resource, self._pin), timeout=10) self._resource, self._pin), timeout=10)

View File

@ -19,7 +19,7 @@ SENSOR_TYPES = {
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the available BloomSky weather binary sensors.""" """Setup the available BloomSky weather binary sensors."""
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
bloomsky = get_component('bloomsky') bloomsky = get_component('bloomsky')
sensors = config.get('monitored_conditions', SENSOR_TYPES) sensors = config.get('monitored_conditions', SENSOR_TYPES)
@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class BloomSkySensor(BinarySensorDevice): class BloomSkySensor(BinarySensorDevice):
""" Represents a single binary sensor in a BloomSky device. """ """Represent a single binary sensor in a BloomSky device."""
def __init__(self, bs, device, sensor_name): def __init__(self, bs, device, sensor_name):
"""Initialize a BloomSky binary sensor.""" """Initialize a BloomSky binary sensor."""
@ -53,7 +53,7 @@ class BloomSkySensor(BinarySensorDevice):
@property @property
def unique_id(self): def unique_id(self):
"""Unique ID for this sensor.""" """Return the unique ID for this sensor."""
return self._unique_id return self._unique_id
@property @property
@ -63,7 +63,7 @@ class BloomSkySensor(BinarySensorDevice):
@property @property
def is_on(self): def is_on(self):
"""If binary sensor is on.""" """Return true if binary sensor is on."""
return self._state return self._state
def update(self): def update(self):

View File

@ -1,6 +1,5 @@
""" """
Allows to configure custom shell commands to turn a value into a logical value Support for custom shell commands to to retrieve values.
for a binary sensor.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.command/ https://home-assistant.io/components/binary_sensor.command/
@ -25,7 +24,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add the Command Sensor.""" """Setup the Command Sensor."""
if config.get('command') is None: if config.get('command') is None:
_LOGGER.error('Missing required variable: "command"') _LOGGER.error('Missing required variable: "command"')
return False return False
@ -44,11 +43,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
class CommandBinarySensor(BinarySensorDevice): class CommandBinarySensor(BinarySensorDevice):
""" """Represent a command line binary sensor."""
Represents a binary sensor that is returning a value of a shell commands.
"""
def __init__(self, hass, data, name, payload_on, def __init__(self, hass, data, name, payload_on,
payload_off, value_template): payload_off, value_template):
"""Initialize the Command line binary sensor."""
self._hass = hass self._hass = hass
self.data = data self.data = data
self._name = name self._name = name
@ -60,16 +59,16 @@ class CommandBinarySensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""The name of the sensor.""" """Return the name of the sensor."""
return self._name return self._name
@property @property
def is_on(self): def is_on(self):
"""True if the binary sensor is on.""" """Return true if the binary sensor is on."""
return self._state return self._state
def update(self): def update(self):
"""Gets the latest data and updates the state.""" """Get the latest data and updates the state."""
self.data.update() self.data.update()
value = self.data.value value = self.data.value

View File

@ -17,7 +17,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoBinarySensor(BinarySensorDevice): class DemoBinarySensor(BinarySensorDevice):
"""A Demo binary sensor.""" """A Demo binary sensor."""
def __init__(self, name, state, sensor_class): def __init__(self, name, state, sensor_class):
"""Initialize the demo sensor."""
self._name = name self._name = name
self._state = state self._state = state
self._sensor_type = sensor_class self._sensor_type = sensor_class

View File

@ -1,5 +1,5 @@
""" """
Allows to configure a MQTT binary sensor. Support for MQTT binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mqtt/ https://home-assistant.io/components/binary_sensor.mqtt/
@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.mqtt/
import logging import logging
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import template from homeassistant.helpers import template
@ -24,15 +25,20 @@ DEPENDENCIES = ['mqtt']
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add MQTT binary sensor.""" """Add MQTT binary sensor."""
if config.get('state_topic') is None: if config.get('state_topic') is None:
_LOGGER.error('Missing required variable: state_topic') _LOGGER.error('Missing required variable: state_topic')
return False return False
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
add_devices([MqttBinarySensor( add_devices([MqttBinarySensor(
hass, hass,
config.get('name', DEFAULT_NAME), config.get('name', DEFAULT_NAME),
config.get('state_topic', None), config.get('state_topic', None),
sensor_class,
config.get('qos', DEFAULT_QOS), config.get('qos', DEFAULT_QOS),
config.get('payload_on', DEFAULT_PAYLOAD_ON), config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF), config.get('payload_off', DEFAULT_PAYLOAD_OFF),
@ -41,13 +47,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttBinarySensor(BinarySensorDevice): class MqttBinarySensor(BinarySensorDevice):
"""Represents a binary sensor that is updated by MQTT.""" """Representation a binary sensor that is updated by MQTT."""
def __init__(self, hass, name, state_topic, qos, payload_on, payload_off,
value_template): def __init__(self, hass, name, state_topic, sensor_class, qos, payload_on,
payload_off, value_template):
"""Initialize the MQTT binary sensor."""
self._hass = hass self._hass = hass
self._name = name self._name = name
self._state = False self._state = False
self._state_topic = state_topic self._state_topic = state_topic
self._sensor_class = sensor_class
self._payload_on = payload_on self._payload_on = payload_on
self._payload_off = payload_off self._payload_off = payload_off
self._qos = qos self._qos = qos
@ -73,10 +82,15 @@ class MqttBinarySensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""The name of the binary sensor.""" """Return the name of the binary sensor."""
return self._name return self._name
@property @property
def is_on(self): def is_on(self):
"""True if the binary sensor is on.""" """Return true if the binary sensor is on."""
return self._state return self._state
@property
def sensor_class(self):
"""Return the class of this sensor."""
return self._sensor_class

View File

@ -90,7 +90,7 @@ class MySensorsBinarySensor(BinarySensorDevice):
@property @property
def should_poll(self): def should_poll(self):
"""MySensor gateway pushes its state to HA.""" """Mysensor gateway pushes its state to HA."""
return False return False
@property @property

View File

@ -26,7 +26,6 @@ BINARY_TYPES = ['fan',
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Nest binary sensors.""" """Setup Nest binary sensors."""
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
for structure in nest.NEST.structures: for structure in nest.NEST.structures:

View File

@ -66,6 +66,7 @@ class NX584ZoneSensor(BinarySensorDevice):
"""Represents a NX584 zone as a sensor.""" """Represents a NX584 zone as a sensor."""
def __init__(self, zone, zone_type): def __init__(self, zone, zone_type):
"""Initialize the nx594 binary sensor."""
self._zone = zone self._zone = zone
self._zone_type = zone_type self._zone_type = zone_type
@ -81,7 +82,7 @@ class NX584ZoneSensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""Name of the binary sensor.""" """Return the name of the binary sensor."""
return self._zone['name'] return self._zone['name']
@property @property
@ -95,6 +96,7 @@ class NX584Watcher(threading.Thread):
"""Event listener thread to process NX584 events.""" """Event listener thread to process NX584 events."""
def __init__(self, client, zone_sensors): def __init__(self, client, zone_sensors):
"""Initialize nx584 watcher thread."""
super(NX584Watcher, self).__init__() super(NX584Watcher, self).__init__()
self.daemon = True self.daemon = True
self._client = client self._client = client
@ -115,7 +117,7 @@ class NX584Watcher(threading.Thread):
self._process_zone_event(event) self._process_zone_event(event)
def _run(self): def _run(self):
# Throw away any existing events so we don't replay history """Throw away any existing events so we don't replay history."""
self._client.get_events() self._client.get_events()
while True: while True:
events = self._client.get_events() events = self._client.get_events()
@ -123,6 +125,7 @@ class NX584Watcher(threading.Thread):
self._process_events(events) self._process_events(events)
def run(self): def run(self):
"""Run the watcher."""
while True: while True:
try: try:
self._run() self._run()

View File

@ -1,5 +1,5 @@
""" """
The rest binary sensor will consume responses sent by an exposed REST API. Support for RESTful binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rest/ https://home-assistant.io/components/binary_sensor.rest/
@ -52,7 +52,7 @@ class RestBinarySensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""Name of the binary sensor.""" """Return the name of the binary sensor."""
return self._name return self._name
@property @property

View File

@ -1,5 +1,5 @@
""" """
Allows to configure a binary sensor using RPi GPIO. Support for binary sensor using RPi GPIO.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rpi_gpio/ https://home-assistant.io/components/binary_sensor.rpi_gpio/
@ -20,8 +20,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Sets up the Raspberry PI GPIO devices.""" """Setup the Raspberry PI GPIO devices."""
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE) pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME) bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC) invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
@ -36,10 +35,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
class RPiGPIOBinarySensor(BinarySensorDevice): class RPiGPIOBinarySensor(BinarySensorDevice):
"""Represents a binary sensor that uses Raspberry Pi GPIO.""" """Represent a binary sensor that uses Raspberry Pi GPIO."""
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
# pylint: disable=no-member
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
"""Initialize the RPi binary sensor."""
# pylint: disable=no-member
self._name = name or DEVICE_DEFAULT_NAME self._name = name or DEVICE_DEFAULT_NAME
self._port = port self._port = port
self._pull_mode = pull_mode self._pull_mode = pull_mode
@ -50,9 +50,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
self._state = rpi_gpio.read_input(self._port) self._state = rpi_gpio.read_input(self._port)
def read_gpio(port): def read_gpio(port):
"""Reads state from GPIO.""" """Read state from GPIO."""
self._state = rpi_gpio.read_input(self._port) self._state = rpi_gpio.read_input(self._port)
self.update_ha_state() self.update_ha_state()
rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime) rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime)
@property @property
@ -62,10 +63,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""The name of the sensor.""" """Return the name of the sensor."""
return self._name return self._name
@property @property
def is_on(self): def is_on(self):
"""Returns the state of the entity.""" """Return the state of the entity."""
return self._state != self._invert_logic return self._state != self._invert_logic

View File

@ -23,6 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class BinarySensor(BinarySensorDevice, Sensor): class BinarySensor(BinarySensorDevice, Sensor):
"""A binary sensor which is on when its state == CONF_VALUE_ON.""" """A binary sensor which is on when its state == CONF_VALUE_ON."""
required = (CONF_VALUE_ON,) required = (CONF_VALUE_ON,)
@property @property

View File

@ -1,7 +1,8 @@
""" """
homeassistant.components.binary_sensor.template Support for exposing a templated binary sensor.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for exposing a templated binary_sensor For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/
""" """
import logging import logging
@ -22,7 +23,6 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup template binary sensors.""" """Setup template binary sensors."""
sensors = [] sensors = []
if config.get(CONF_SENSORS) is None: if config.get(CONF_SENSORS) is None:
_LOGGER.error('Missing configuration data for binary_sensor platform') _LOGGER.error('Missing configuration data for binary_sensor platform')
@ -70,11 +70,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class BinarySensorTemplate(BinarySensorDevice): class BinarySensorTemplate(BinarySensorDevice):
"""A virtual binary_sensor that triggers from another sensor.""" """A virtual binary sensor that triggers from another sensor."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, hass, device, friendly_name, sensor_class, def __init__(self, hass, device, friendly_name, sensor_class,
value_template): value_template):
"""Initialize the Template binary sensor."""
self._hass = hass self._hass = hass
self._device = device self._device = device
self._name = friendly_name self._name = friendly_name
@ -90,25 +91,32 @@ class BinarySensorTemplate(BinarySensorDevice):
hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
def _event_listener(self, event): def _event_listener(self, event):
if not hasattr(self, 'hass'):
return
self.update_ha_state(True) self.update_ha_state(True)
@property @property
def should_poll(self): def should_poll(self):
"""No polling needed."""
return False return False
@property @property
def sensor_class(self): def sensor_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class return self._sensor_class
@property @property
def name(self): def name(self):
"""Return the name of the sensor."""
return self._name return self._name
@property @property
def is_on(self): def is_on(self):
"""Return true if sensor is on."""
return self._state return self._state
def update(self): def update(self):
"""Get the latest data and update the state."""
try: try:
value = template.render(self._hass, self._template) value = template.render(self._hass, self._template)
except TemplateError as ex: except TemplateError as ex:

View File

@ -0,0 +1,75 @@
"""
Support for WeMo sensors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.wemo/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.loader import get_component
DEPENDENCIES = ['wemo']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument, too-many-function-args
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Register discovered WeMo binary sensors."""
import pywemo.discovery as discovery
if discovery_info is not None:
location = discovery_info[2]
mac = discovery_info[3]
device = discovery.device_from_description(location, mac)
if device:
add_devices_callback([WemoBinarySensor(device)])
class WemoBinarySensor(BinarySensorDevice):
"""Represents a WeMo binary sensor."""
def __init__(self, device):
"""Initialize the WeMo sensor."""
self.wemo = device
self._state = None
wemo = get_component('wemo')
wemo.SUBSCRIPTION_REGISTRY.register(self.wemo)
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)
def _update_callback(self, _device, _params):
"""Called by the wemo device callback to update state."""
_LOGGER.info(
'Subscription update for %s',
_device)
self.update_ha_state(True)
@property
def should_poll(self):
"""No polling needed with subscriptions."""
return False
@property
def unique_id(self):
"""Return the id of this WeMo device."""
return "{}.{}".format(self.__class__, self.wemo.serialnumber)
@property
def name(self):
"""Return the name of the sevice if any."""
return self.wemo.name
@property
def is_on(self):
"""True if sensor is on."""
return self._state
def update(self):
"""Update WeMo state."""
try:
self._state = self.wemo.get_state(True)
except AttributeError:
_LOGGER.warning('Could not update status for %s', self.name)

View File

@ -22,7 +22,7 @@ SENSOR_TYPES = {
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Sets up the Wink platform.""" """Setup the Wink platform."""
import pywink import pywink
if discovery_info is None: if discovery_info is None:
@ -42,16 +42,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkBinarySensorDevice(BinarySensorDevice, Entity): class WinkBinarySensorDevice(BinarySensorDevice, Entity):
"""Represents a Wink sensor.""" """Representation of a Wink sensor."""
def __init__(self, wink): def __init__(self, wink):
"""Initialize the Wink binary sensor."""
self.wink = wink self.wink = wink
self._unit_of_measurement = self.wink.UNIT self._unit_of_measurement = self.wink.UNIT
self.capability = self.wink.capability() self.capability = self.wink.capability()
@property @property
def is_on(self): def is_on(self):
"""Return True if the binary sensor is on.""" """Return true if the binary sensor is on."""
if self.capability == "loudness": if self.capability == "loudness":
return self.wink.loudness_boolean() return self.wink.loudness_boolean()
elif self.capability == "vibration": elif self.capability == "vibration":
@ -68,14 +69,14 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
@property @property
def unique_id(self): def unique_id(self):
""" Returns the id of this wink sensor """ """Return the ID of this wink sensor."""
return "{}.{}".format(self.__class__, self.wink.device_id()) return "{}.{}".format(self.__class__, self.wink.device_id())
@property @property
def name(self): def name(self):
""" Returns the name of the sensor if any. """ """Return the name of the sensor if any."""
return self.wink.name() return self.wink.name()
def update(self): def update(self):
""" Update state of the sensor. """ """Update state of the sensor."""
self.wink.update_state() self.wink.update_state()

View File

@ -19,8 +19,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice): class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):
""" """Use ZigBeeDigitalIn as binary sensor."""
Use multiple inheritance to turn a ZigBeeDigitalIn into a
BinarySensorDevice.
"""
pass pass

View File

@ -23,17 +23,19 @@ DEPENDENCIES = []
PHILIO = 0x013c PHILIO = 0x013c
PHILIO_SLIM_SENSOR = 0x0002 PHILIO_SLIM_SENSOR = 0x0002
PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0) PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0)
WENZHOU = 0x0118
WENZHOU_SLIM_SENSOR_MOTION = (WENZHOU, PHILIO_SLIM_SENSOR, 0)
WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event' WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event'
DEVICE_MAPPINGS = { DEVICE_MAPPINGS = {
PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT, PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
WENZHOU_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
} }
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for sensors.""" """Setup the Z-Wave platform for sensors."""
if discovery_info is None or NETWORK is None: if discovery_info is None or NETWORK is None:
return return
@ -63,9 +65,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
"""Represents a binary sensor within Z-Wave.""" """Representation of a binary sensor within Z-Wave."""
def __init__(self, value, sensor_class): def __init__(self, value, sensor_class):
"""Initialize the sensor."""
self._sensor_type = sensor_class self._sensor_type = sensor_class
# pylint: disable=import-error # pylint: disable=import-error
from openzwave.network import ZWaveNetwork from openzwave.network import ZWaveNetwork
@ -98,12 +101,10 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
class ZWaveTriggerSensor(ZWaveBinarySensor): class ZWaveTriggerSensor(ZWaveBinarySensor):
""" """Representation of a stateless sensor within Z-Wave."""
Represents a stateless sensor which triggers events just 'On'
within Z-Wave.
"""
def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60): def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60):
"""Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(sensor_value, sensor_class) super(ZWaveTriggerSensor, self).__init__(sensor_value, sensor_class)
self._hass = hass self._hass = hass
self.re_arm_sec = re_arm_sec self.re_arm_sec = re_arm_sec

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.bloomsky
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for BloomSky weather station. Support for BloomSky weather station.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
@ -32,7 +30,7 @@ DISCOVER_CAMERAS = 'bloomsky.camera'
# pylint: disable=unused-argument,too-few-public-methods # pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config): def setup(hass, config):
""" Setup BloomSky component. """ """Setup BloomSky component."""
if not validate_config( if not validate_config(
config, config,
{DOMAIN: [CONF_API_KEY]}, {DOMAIN: [CONF_API_KEY]},
@ -57,13 +55,13 @@ def setup(hass, config):
class BloomSky(object): class BloomSky(object):
""" Handle all communication with the BloomSky API. """ """Handle all communication with the BloomSky API."""
# API documentation at http://weatherlution.com/bloomsky-api/ # API documentation at http://weatherlution.com/bloomsky-api/
API_URL = "https://api.bloomsky.com/api/skydata" API_URL = "https://api.bloomsky.com/api/skydata"
def __init__(self, api_key): def __init__(self, api_key):
"""Initialize the BookSky."""
self._api_key = api_key self._api_key = api_key
self.devices = {} self.devices = {}
_LOGGER.debug("Initial bloomsky device load...") _LOGGER.debug("Initial bloomsky device load...")
@ -71,10 +69,7 @@ class BloomSky(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def refresh_devices(self): def refresh_devices(self):
""" """Use the API to retreive a list of devices."""
Uses the API to retreive a list of devices associated with an
account along with all the sensors on the device.
"""
_LOGGER.debug("Fetching bloomsky update") _LOGGER.debug("Fetching bloomsky update")
response = requests.get(self.API_URL, response = requests.get(self.API_URL,
headers={"Authorization": self._api_key}, headers={"Authorization": self._api_key},
@ -84,7 +79,7 @@ class BloomSky(object):
elif response.status_code != 200: elif response.status_code != 200:
_LOGGER.error("Invalid HTTP response: %s", response.status_code) _LOGGER.error("Invalid HTTP response: %s", response.status_code)
return return
# create dictionary keyed off of the device unique id # Create dictionary keyed off of the device unique id
self.devices.update({ self.devices.update({
device["DeviceID"]: device for device in response.json() device["DeviceID"]: device for device in response.json()
}) })

View File

@ -10,9 +10,7 @@ SERVICE_BROWSE_URL = "browse_url"
def setup(hass, config): def setup(hass, config):
""" """Listen for browse_url events."""
Listen for browse_url events and open the url in the default web browser.
"""
import webbrowser import webbrowser
hass.services.register(DOMAIN, SERVICE_BROWSE_URL, hass.services.register(DOMAIN, SERVICE_BROWSE_URL,

View File

@ -42,7 +42,7 @@ MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n'
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def setup(hass, config): def setup(hass, config):
"""Initialize camera component.""" """Setup the camera component."""
component = EntityComponent( component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
DISCOVERY_PLATFORMS) DISCOVERY_PLATFORMS)

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.camera.bloomsky
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for a camera of a BloomSky weather station. Support for a camera of a BloomSky weather station.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
@ -18,17 +16,17 @@ DEPENDENCIES = ["bloomsky"]
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" set up access to BloomSky cameras """ """Setup access to BloomSky cameras."""
bloomsky = get_component('bloomsky') bloomsky = get_component('bloomsky')
for device in bloomsky.BLOOMSKY.devices.values(): for device in bloomsky.BLOOMSKY.devices.values():
add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)]) add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
class BloomSkyCamera(Camera): class BloomSkyCamera(Camera):
""" Represents the images published from the BloomSky's camera. """ """Representation of the images published from the BloomSky's camera."""
def __init__(self, bs, device): def __init__(self, bs, device):
""" set up for access to the BloomSky camera images """ """Setup for access to the BloomSky camera images."""
super(BloomSkyCamera, self).__init__() super(BloomSkyCamera, self).__init__()
self._name = device["DeviceName"] self._name = device["DeviceName"]
self._id = device["DeviceID"] self._id = device["DeviceID"]
@ -37,16 +35,16 @@ class BloomSkyCamera(Camera):
self._last_url = "" self._last_url = ""
# _last_image will store images as they are downloaded so that the # _last_image will store images as they are downloaded so that the
# frequent updates in home-assistant don't keep poking the server # frequent updates in home-assistant don't keep poking the server
# to download the same image over and over # to download the same image over and over.
self._last_image = "" self._last_image = ""
self._logger = logging.getLogger(__name__) self._logger = logging.getLogger(__name__)
def camera_image(self): def camera_image(self):
""" Update the camera's image if it has changed. """ """Update the camera's image if it has changed."""
try: try:
self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"] self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"]
self._bloomsky.refresh_devices() self._bloomsky.refresh_devices()
# if the url hasn't changed then the image hasn't changed # If the URL hasn't changed then the image hasn't changed.
if self._url != self._last_url: if self._url != self._last_url:
response = requests.get(self._url, timeout=10) response = requests.get(self._url, timeout=10)
self._last_url = self._url self._last_url = self._url
@ -59,5 +57,5 @@ class BloomSkyCamera(Camera):
@property @property
def name(self): def name(self):
""" The name of this BloomSky device. """ """Return the name of this BloomSky device."""
return self._name return self._name

View File

@ -21,6 +21,7 @@ class DemoCamera(Camera):
"""A Demo camera.""" """A Demo camera."""
def __init__(self, name): def __init__(self, name):
"""Initialize demo camera component."""
super().__init__() super().__init__()
self._name = name self._name = name

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.camera.foscam
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component provides basic support for Foscam IP cameras. This component provides basic support for Foscam IP cameras.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
@ -18,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Adds a Foscam IP Camera. """ """Setup a Foscam IP Camera."""
if not validate_config({DOMAIN: config}, if not validate_config({DOMAIN: config},
{DOMAIN: ['username', 'password', 'ip']}, _LOGGER): {DOMAIN: ['username', 'password', 'ip']}, _LOGGER):
return None return None
@ -28,9 +26,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class FoscamCamera(Camera): class FoscamCamera(Camera):
""" An implementation of a Foscam IP camera. """ """An implementation of a Foscam IP camera."""
def __init__(self, device_info): def __init__(self, device_info):
"""Initialize a Foscam camera."""
super(FoscamCamera, self).__init__() super(FoscamCamera, self).__init__()
ip_address = device_info.get('ip') ip_address = device_info.get('ip')
@ -48,8 +47,7 @@ class FoscamCamera(Camera):
self._name, self._snap_picture_url) self._name, self._snap_picture_url)
def camera_image(self): def camera_image(self):
""" Return a still image reponse from the camera. """ """Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data # Send the request to snap a picture and return raw jpg data
response = requests.get(self._snap_picture_url) response = requests.get(self._snap_picture_url)
@ -57,5 +55,5 @@ class FoscamCamera(Camera):
@property @property
def name(self): def name(self):
""" Return the name of this device. """ """Return the name of this camera."""
return self._name return self._name

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.camera.generic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras. Support for IP Cameras.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
@ -19,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Adds a generic IP Camera. """ """Setup a generic IP Camera."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['still_image_url']}, if not validate_config({DOMAIN: config}, {DOMAIN: ['still_image_url']},
_LOGGER): _LOGGER):
return None return None
@ -29,11 +27,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class GenericCamera(Camera): class GenericCamera(Camera):
""" """A generic implementation of an IP camera."""
A generic implementation of an IP camera that is reachable over a URL.
"""
def __init__(self, device_info): def __init__(self, device_info):
"""Initialize a generic camera."""
super().__init__() super().__init__()
self._name = device_info.get('name', 'Generic Camera') self._name = device_info.get('name', 'Generic Camera')
self._username = device_info.get('username') self._username = device_info.get('username')
@ -41,7 +38,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info['still_image_url'] self._still_image_url = device_info['still_image_url']
def camera_image(self): def camera_image(self):
""" Return a still image response from the camera. """ """Return a still image response from the camera."""
if self._username and self._password: if self._username and self._password:
try: try:
response = requests.get( response = requests.get(
@ -61,5 +58,5 @@ class GenericCamera(Camera):
@property @property
def name(self): def name(self):
""" Return the name of this device. """ """Return the name of this device."""
return self._name return self._name

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.camera.mjpeg
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras. Support for IP Cameras.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
@ -23,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Adds a mjpeg IP Camera. """ """Setup a MJPEG IP Camera."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['mjpeg_url']}, if not validate_config({DOMAIN: config}, {DOMAIN: ['mjpeg_url']},
_LOGGER): _LOGGER):
return None return None
@ -33,11 +31,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class MjpegCamera(Camera): class MjpegCamera(Camera):
""" """An implementation of an IP camera that is reachable over a URL."""
A generic implementation of an IP camera that is reachable over a URL.
"""
def __init__(self, device_info): def __init__(self, device_info):
"""Initialize a MJPEG camera."""
super().__init__() super().__init__()
self._name = device_info.get('name', 'Mjpeg Camera') self._name = device_info.get('name', 'Mjpeg Camera')
self._username = device_info.get('username') self._username = device_info.get('username')
@ -45,7 +42,7 @@ class MjpegCamera(Camera):
self._mjpeg_url = device_info['mjpeg_url'] self._mjpeg_url = device_info['mjpeg_url']
def camera_stream(self): def camera_stream(self):
""" Return a mjpeg stream image response directly from the camera. """ """Return a MJPEG stream image response directly from the camera."""
if self._username and self._password: if self._username and self._password:
return requests.get(self._mjpeg_url, return requests.get(self._mjpeg_url,
auth=HTTPBasicAuth(self._username, auth=HTTPBasicAuth(self._username,
@ -56,10 +53,9 @@ class MjpegCamera(Camera):
stream=True) stream=True)
def camera_image(self): def camera_image(self):
""" Return a still image response from the camera. """ """Return a still image response from the camera."""
def process_response(response): def process_response(response):
""" Take in a response object, return the jpg from it. """ """Take in a response object, return the jpg from it."""
data = b'' data = b''
for chunk in response.iter_content(1024): for chunk in response.iter_content(1024):
data += chunk data += chunk
@ -73,7 +69,7 @@ class MjpegCamera(Camera):
return process_response(response) return process_response(response)
def mjpeg_stream(self, handler): def mjpeg_stream(self, handler):
""" Generate an HTTP MJPEG stream from the camera. """ """Generate an HTTP MJPEG stream from the camera."""
response = self.camera_stream() response = self.camera_stream()
content_type = response.headers[CONTENT_TYPE_HEADER] content_type = response.headers[CONTENT_TYPE_HEADER]
@ -88,5 +84,5 @@ class MjpegCamera(Camera):
@property @property
def name(self): def name(self):
""" Return the name of this device. """ """Return the name of this camera."""
return self._name return self._name

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.camera.uvc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Ubiquiti's UVC cameras. Support for Ubiquiti's UVC cameras.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
@ -20,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Discover cameras on a Unifi NVR. """ """Discover cameras on a Unifi NVR."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']}, if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']},
_LOGGER): _LOGGER):
return None return None
@ -60,9 +58,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class UnifiVideoCamera(Camera): class UnifiVideoCamera(Camera):
""" A Ubiquiti Unifi Video Camera. """ """A Ubiquiti Unifi Video Camera."""
def __init__(self, nvr, uuid, name): def __init__(self, nvr, uuid, name):
"""Initialize an Unifi camera."""
super(UnifiVideoCamera, self).__init__() super(UnifiVideoCamera, self).__init__()
self._nvr = nvr self._nvr = nvr
self._uuid = uuid self._uuid = uuid
@ -73,23 +72,28 @@ class UnifiVideoCamera(Camera):
@property @property
def name(self): def name(self):
"""Return the name of this camera."""
return self._name return self._name
@property @property
def is_recording(self): def is_recording(self):
"""Return true if the camera is recording."""
caminfo = self._nvr.get_camera(self._uuid) caminfo = self._nvr.get_camera(self._uuid)
return caminfo['recordingSettings']['fullTimeRecordEnabled'] return caminfo['recordingSettings']['fullTimeRecordEnabled']
@property @property
def brand(self): def brand(self):
"""Return the brand of this camera."""
return 'Ubiquiti' return 'Ubiquiti'
@property @property
def model(self): def model(self):
"""Return the model of this camera."""
caminfo = self._nvr.get_camera(self._uuid) caminfo = self._nvr.get_camera(self._uuid)
return caminfo['model'] return caminfo['model']
def _login(self): def _login(self):
"""Login to the camera."""
from uvcclient import camera as uvc_camera from uvcclient import camera as uvc_camera
from uvcclient import store as uvc_store from uvcclient import store as uvc_store
@ -131,6 +135,7 @@ class UnifiVideoCamera(Camera):
return True return True
def camera_image(self): def camera_image(self):
"""Return the image of this camera."""
from uvcclient import camera as uvc_camera from uvcclient import camera as uvc_camera
if not self._camera: if not self._camera:
if not self._login(): if not self._login():

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.configurator Support to allow pieces of code to request configuration from the user.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A component to allow pieces of code to request configuration from the user.
Initiate a request by calling the `request_config` method with a callback. Initiate a request by calling the `request_config` method with a callback.
This will return a request id that has to be used for future calls. This will return a request id that has to be used for future calls.
@ -38,9 +35,10 @@ _LOGGER = logging.getLogger(__name__)
def request_config( def request_config(
hass, name, callback, description=None, description_image=None, hass, name, callback, description=None, description_image=None,
submit_caption=None, fields=None): submit_caption=None, fields=None):
""" Create a new request for config. """Create a new request for configuration.
Will return an ID to be used for sequent calls. """
Will return an ID to be used for sequent calls.
"""
instance = _get_instance(hass) instance = _get_instance(hass)
request_id = instance.request_config( request_id = instance.request_config(
@ -53,7 +51,7 @@ def request_config(
def notify_errors(request_id, error): def notify_errors(request_id, error):
""" Add errors to a config request. """ """Add errors to a config request."""
try: try:
_REQUESTS[request_id].notify_errors(request_id, error) _REQUESTS[request_id].notify_errors(request_id, error)
except KeyError: except KeyError:
@ -62,7 +60,7 @@ def notify_errors(request_id, error):
def request_done(request_id): def request_done(request_id):
""" Mark a config request as done. """ """Mark a configuration request as done."""
try: try:
_REQUESTS.pop(request_id).request_done(request_id) _REQUESTS.pop(request_id).request_done(request_id)
except KeyError: except KeyError:
@ -71,12 +69,12 @@ def request_done(request_id):
def setup(hass, config): def setup(hass, config):
""" Set up Configurator. """ """Setup the configurator component."""
return True return True
def _get_instance(hass): def _get_instance(hass):
""" Get an instance per hass object. """ """Get an instance per hass object."""
try: try:
return _INSTANCES[hass] return _INSTANCES[hass]
except KeyError: except KeyError:
@ -89,11 +87,10 @@ def _get_instance(hass):
class Configurator(object): class Configurator(object):
""" """The class to keep track of current configuration requests."""
Class to keep track of current configuration requests.
"""
def __init__(self, hass): def __init__(self, hass):
"""Initialize the configurator."""
self.hass = hass self.hass = hass
self._cur_id = 0 self._cur_id = 0
self._requests = {} self._requests = {}
@ -104,8 +101,7 @@ class Configurator(object):
def request_config( def request_config(
self, name, callback, self, name, callback,
description, description_image, submit_caption, fields): description, description_image, submit_caption, fields):
""" Setup a request for configuration. """ """Setup a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass) entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
if fields is None: if fields is None:
@ -133,7 +129,7 @@ class Configurator(object):
return request_id return request_id
def notify_errors(self, request_id, error): def notify_errors(self, request_id, error):
""" Update the state with errors. """ """Update the state with errors."""
if not self._validate_request_id(request_id): if not self._validate_request_id(request_id):
return return
@ -147,7 +143,7 @@ class Configurator(object):
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data) self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
def request_done(self, request_id): def request_done(self, request_id):
""" Remove the config request. """ """Remove the configuration request."""
if not self._validate_request_id(request_id): if not self._validate_request_id(request_id):
return return
@ -160,13 +156,13 @@ class Configurator(object):
self.hass.states.set(entity_id, STATE_CONFIGURED) self.hass.states.set(entity_id, STATE_CONFIGURED)
def deferred_remove(event): def deferred_remove(event):
""" Remove the request state. """ """Remove the request state."""
self.hass.states.remove(entity_id) self.hass.states.remove(entity_id)
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove) self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
def handle_service_call(self, call): def handle_service_call(self, call):
""" Handle a configure service call. """ """Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID) request_id = call.data.get(ATTR_CONFIGURE_ID)
if not self._validate_request_id(request_id): if not self._validate_request_id(request_id):
@ -180,10 +176,10 @@ class Configurator(object):
callback(call.data.get(ATTR_FIELDS, {})) callback(call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self): def _generate_unique_id(self):
""" Generates a unique configurator id. """ """Generate a unique configurator ID."""
self._cur_id += 1 self._cur_id += 1
return "{}-{}".format(id(self), self._cur_id) return "{}-{}".format(id(self), self._cur_id)
def _validate_request_id(self, request_id): def _validate_request_id(self, request_id):
""" Validate that the request belongs to this instance. """ """Validate that the request belongs to this instance."""
return request_id in self._requests return request_id in self._requests

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.conversation Support for functionality to have conversations with Home Assistant.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to have conversations with Home Assistant.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/conversation/ https://home-assistant.io/components/conversation/
@ -25,19 +23,18 @@ REQUIREMENTS = ['fuzzywuzzy==0.8.0']
def setup(hass, config): def setup(hass, config):
""" Registers the process service. """ """Register the process service."""
from fuzzywuzzy import process as fuzzyExtract from fuzzywuzzy import process as fuzzyExtract
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def process(service): def process(service):
""" Parses text into commands for Home Assistant. """ """Parse text into commands."""
if ATTR_TEXT not in service.data: if ATTR_TEXT not in service.data:
logger.error("Received process service call without a text") logger.error("Received process service call without a text")
return return
text = service.data[ATTR_TEXT].lower() text = service.data[ATTR_TEXT].lower()
match = REGEX_TURN_COMMAND.match(text) match = REGEX_TURN_COMMAND.match(text)
if not match: if not match:
@ -45,11 +42,8 @@ def setup(hass, config):
return return
name, command = match.groups() name, command = match.groups()
entities = {state.entity_id: state.name for state in hass.states.all()} entities = {state.entity_id: state.name for state in hass.states.all()}
entity_ids = fuzzyExtract.extractOne(name, entities,
entity_ids = fuzzyExtract.extractOne(name,
entities,
score_cutoff=65)[2] score_cutoff=65)[2]
if not entity_ids: if not entity_ids:

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_sun_light_trigger Provides functionality to turn on lights based on the states.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to turn on lights based on the state of the sun and
devices.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_sun_light_trigger/ https://home-assistant.io/components/device_sun_light_trigger/
@ -12,9 +9,9 @@ from datetime import timedelta
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from homeassistant.helpers.event import track_point_in_time, track_state_change from homeassistant.helpers.event import track_point_in_time
from homeassistant.helpers.event_decorators import track_state_change
from . import device_tracker, group, light, sun from homeassistant.loader import get_component
DOMAIN = "device_sun_light_trigger" DOMAIN = "device_sun_light_trigger"
DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun'] DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun']
@ -29,28 +26,26 @@ CONF_LIGHT_GROUP = 'light_group'
CONF_DEVICE_GROUP = 'device_group' CONF_DEVICE_GROUP = 'device_group'
# pylint: disable=too-many-branches # pylint: disable=too-many-locals
def setup(hass, config): def setup(hass, config):
""" Triggers to turn lights on or off based on device precense. """ """The triggers to turn lights on or off based on device presence."""
logger = logging.getLogger(__name__)
device_tracker = get_component('device_tracker')
group = get_component('group')
light = get_component('light')
sun = get_component('sun')
disable_turn_off = 'disable_turn_off' in config[DOMAIN] disable_turn_off = 'disable_turn_off' in config[DOMAIN]
light_group = config[DOMAIN].get(CONF_LIGHT_GROUP, light_group = config[DOMAIN].get(CONF_LIGHT_GROUP,
light.ENTITY_ID_ALL_LIGHTS) light.ENTITY_ID_ALL_LIGHTS)
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE, LIGHT_PROFILE) light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE, LIGHT_PROFILE)
device_group = config[DOMAIN].get(CONF_DEVICE_GROUP, device_group = config[DOMAIN].get(CONF_DEVICE_GROUP,
device_tracker.ENTITY_ID_ALL_DEVICES) device_tracker.ENTITY_ID_ALL_DEVICES)
logger = logging.getLogger(__name__)
device_entity_ids = group.get_entity_ids(hass, device_group, device_entity_ids = group.get_entity_ids(hass, device_group,
device_tracker.DOMAIN) device_tracker.DOMAIN)
if not device_entity_ids: if not device_entity_ids:
logger.error("No devices found to track") logger.error("No devices found to track")
return False return False
# Get the light IDs from the specified group # Get the light IDs from the specified group
@ -58,77 +53,75 @@ def setup(hass, config):
if not light_ids: if not light_ids:
logger.error("No lights found to turn on ") logger.error("No lights found to turn on ")
return False return False
def calc_time_for_light_when_sunset(): def calc_time_for_light_when_sunset():
""" Calculates the time when to start fading lights in when sun sets. """Calculate the time when to start fading lights in when sun sets.
Returns None if no next_setting data available. """
Returns None if no next_setting data available.
"""
next_setting = sun.next_setting(hass) next_setting = sun.next_setting(hass)
if not next_setting:
if next_setting:
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
else:
return None return None
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
def schedule_light_on_sun_rise(entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on.
We will schedule to have each light start after one another
and slowly transition in."""
def turn_light_on_before_sunset(light_id): def turn_light_on_before_sunset(light_id):
""" Helper function to turn on lights slowly if there """Helper function to turn on lights.
are devices home and the light is not on yet. """
if device_tracker.is_on(hass) and not light.is_on(hass, light_id):
Speed is slow if there are devices home and the light is not on yet.
"""
if not device_tracker.is_on(hass) or light.is_on(hass, light_id):
return
light.turn_on(hass, light_id, light.turn_on(hass, light_id,
transition=LIGHT_TRANSITION_TIME.seconds, transition=LIGHT_TRANSITION_TIME.seconds,
profile=light_profile) profile=light_profile)
def turn_on(light_id):
""" Lambda can keep track of function parameters but not local
parameters. If we put the lambda directly in the below statement
only the last light will be turned on.. """
return lambda now: turn_light_on_before_sunset(light_id)
start_point = calc_time_for_light_when_sunset()
if start_point:
for index, light_id in enumerate(light_ids):
track_point_in_time(
hass, turn_on(light_id),
(start_point + index * LIGHT_TRANSITION_TIME))
# Track every time sun rises so we can schedule a time-based # Track every time sun rises so we can schedule a time-based
# pre-sun set event # pre-sun set event
track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise, @track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) sun.STATE_ABOVE_HORIZON)
def schedule_lights_at_sun_set(hass, entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on.
# If the sun is already above horizon We will schedule to have each light start after one another
# schedule the time-based pre-sun set event and slowly transition in.
"""
start_point = calc_time_for_light_when_sunset()
if not start_point:
return
def turn_on(light_id):
"""Lambda can keep track of function parameters.
No local parameters. If we put the lambda directly in the below
statement only the last light will be turned on.
"""
return lambda now: turn_light_on_before_sunset(light_id)
for index, light_id in enumerate(light_ids):
track_point_in_time(hass, turn_on(light_id),
start_point + index * LIGHT_TRANSITION_TIME)
# If the sun is already above horizon schedule the time-based pre-sun set
# event.
if sun.is_on(hass): if sun.is_on(hass):
schedule_light_on_sun_rise(None, None, None) schedule_lights_at_sun_set(hass, None, None, None)
def check_light_on_dev_state_change(entity, old_state, new_state): @track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME)
""" Function to handle tracked device state changes. """ def check_light_on_dev_state_change(hass, entity, old_state, new_state):
"""Handle tracked device state changes."""
# pylint: disable=unused-variable
lights_are_on = group.is_on(hass, light_group) lights_are_on = group.is_on(hass, light_group)
light_needed = not (lights_are_on or sun.is_on(hass)) light_needed = not (lights_are_on or sun.is_on(hass))
# Specific device came home ?
if entity != device_tracker.ENTITY_ID_ALL_DEVICES and \
new_state.state == STATE_HOME:
# These variables are needed for the elif check # These variables are needed for the elif check
now = dt_util.now() now = dt_util.now()
start_point = calc_time_for_light_when_sunset() start_point = calc_time_for_light_when_sunset()
# Do we need lights? # Do we need lights?
if light_needed: if light_needed:
logger.info("Home coming event for %s. Turning lights on", entity)
logger.info(
"Home coming event for %s. Turning lights on", entity)
light.turn_on(hass, light_ids, profile=light_profile) light.turn_on(hass, light_ids, profile=light_profile)
# Are we in the time span were we would turn on the lights # Are we in the time span were we would turn on the lights
@ -141,7 +134,6 @@ def setup(hass, config):
# Check for every light if it would be on if someone was home # Check for every light if it would be on if someone was home
# when the fading in started and turn it on if so # when the fading in started and turn it on if so
for index, light_id in enumerate(light_ids): for index, light_id in enumerate(light_ids):
if now > start_point + index * LIGHT_TRANSITION_TIME: if now > start_point + index * LIGHT_TRANSITION_TIME:
light.turn_on(hass, light_id) light.turn_on(hass, light_id)
@ -150,24 +142,16 @@ def setup(hass, config):
# will all the following then, break. # will all the following then, break.
break break
# Did all devices leave the house? if not disable_turn_off:
elif (entity == device_group and @track_state_change(device_group, STATE_HOME, STATE_NOT_HOME)
new_state.state == STATE_NOT_HOME and lights_are_on and def turn_off_lights_when_all_leave(hass, entity, old_state, new_state):
not disable_turn_off): """Handle device group state change."""
# pylint: disable=unused-variable
if not group.is_on(hass, light_group):
return
logger.info( logger.info(
"Everyone has left but there are lights on. Turning them off") "Everyone has left but there are lights on. Turning them off")
light.turn_off(hass, light_ids) light.turn_off(hass, light_ids)
# Track home coming of each device
track_state_change(
hass, device_entity_ids, check_light_on_dev_state_change,
STATE_NOT_HOME, STATE_HOME)
# Track when all devices are gone to shut down lights
track_state_change(
hass, device_group, check_light_on_dev_state_change,
STATE_HOME, STATE_NOT_HOME)
return True return True

View File

@ -1,14 +1,11 @@
""" """
homeassistant.components.device_tracker Provide functionality to keep track of devices.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to keep track of devices.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_tracker/ https://home-assistant.io/components/device_tracker/
""" """
# pylint: disable=too-many-instance-attributes, too-many-arguments # pylint: disable=too-many-instance-attributes, too-many-arguments
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
import csv
from datetime import timedelta from datetime import timedelta
import logging import logging
import os import os
@ -36,7 +33,6 @@ ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
CSV_DEVICES = "known_devices.csv"
YAML_DEVICES = 'known_devices.yaml' YAML_DEVICES = 'known_devices.yaml'
CONF_TRACK_NEW = "track_new_devices" CONF_TRACK_NEW = "track_new_devices"
@ -72,7 +68,7 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
""" Returns if any or specified device is home. """ """Return the state if any or a specified device is home."""
entity = entity_id or ENTITY_ID_ALL_DEVICES entity = entity_id or ENTITY_ID_ALL_DEVICES
return hass.states.is_state(entity, STATE_HOME) return hass.states.is_state(entity, STATE_HOME)
@ -80,23 +76,21 @@ def is_on(hass, entity_id=None):
def see(hass, mac=None, dev_id=None, host_name=None, location_name=None, def see(hass, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None, gps_accuracy=None, battery=None): gps=None, gps_accuracy=None, battery=None):
""" Call service to notify you see device. """ """Call service to notify you see device."""
data = {key: value for key, value in data = {key: value for key, value in
((ATTR_MAC, mac), ((ATTR_MAC, mac),
(ATTR_DEV_ID, dev_id), (ATTR_DEV_ID, dev_id),
(ATTR_HOST_NAME, host_name), (ATTR_HOST_NAME, host_name),
(ATTR_LOCATION_NAME, location_name), (ATTR_LOCATION_NAME, location_name),
(ATTR_GPS, gps)) if value is not None} (ATTR_GPS, gps),
(ATTR_GPS_ACCURACY, gps_accuracy),
(ATTR_BATTERY, battery)) if value is not None}
hass.services.call(DOMAIN, SERVICE_SEE, data) hass.services.call(DOMAIN, SERVICE_SEE, data)
def setup(hass, config): def setup(hass, config):
""" Setup device tracker """ """Setup device tracker."""
yaml_path = hass.config.path(YAML_DEVICES) 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)
conf = config.get(DOMAIN, {}) conf = config.get(DOMAIN, {})
if isinstance(conf, list): if isinstance(conf, list):
@ -114,7 +108,7 @@ def setup(hass, config):
devices) devices)
def setup_platform(p_type, p_config, disc_info=None): def setup_platform(p_type, p_config, disc_info=None):
""" Setup a device tracker platform. """ """Setup a device tracker platform."""
platform = prepare_setup_platform(hass, config, DOMAIN, p_type) platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
if platform is None: if platform is None:
return return
@ -140,21 +134,21 @@ def setup(hass, config):
setup_platform(p_type, p_config) setup_platform(p_type, p_config)
def device_tracker_discovered(service, info): def device_tracker_discovered(service, info):
""" Called when a device tracker platform is discovered. """ """Called when a device tracker platform is discovered."""
setup_platform(DISCOVERY_PLATFORMS[service], {}, info) setup_platform(DISCOVERY_PLATFORMS[service], {}, info)
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), discovery.listen(hass, DISCOVERY_PLATFORMS.keys(),
device_tracker_discovered) device_tracker_discovered)
def update_stale(now): def update_stale(now):
""" Clean up stale devices. """ """Clean up stale devices."""
tracker.update_stale(now) tracker.update_stale(now)
track_utc_time_change(hass, update_stale, second=range(0, 60, 5)) track_utc_time_change(hass, update_stale, second=range(0, 60, 5))
tracker.setup_group() tracker.setup_group()
def see_service(call): def see_service(call):
""" Service to see a device. """ """Service to see a device."""
args = {key: value for key, value in call.data.items() if key in 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_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)} ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)}
@ -169,8 +163,10 @@ def setup(hass, config):
class DeviceTracker(object): class DeviceTracker(object):
""" Track devices """ """Representation of a device tracker."""
def __init__(self, hass, consider_home, track_new, home_range, devices): def __init__(self, hass, consider_home, track_new, home_range, devices):
"""Initialize a device tracker."""
self.hass = hass self.hass = hass
self.devices = {dev.dev_id: dev for dev in devices} 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.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
@ -187,7 +183,7 @@ class DeviceTracker(object):
def see(self, mac=None, dev_id=None, host_name=None, location_name=None, def see(self, mac=None, dev_id=None, host_name=None, location_name=None,
gps=None, gps_accuracy=None, battery=None): gps=None, gps_accuracy=None, battery=None):
""" Notify device tracker that you see a device. """ """Notify the device tracker that you see a device."""
with self.lock: with self.lock:
if mac is None and dev_id is None: if mac is None and dev_id is None:
raise HomeAssistantError('Neither mac or device id passed in') raise HomeAssistantError('Neither mac or device id passed in')
@ -226,14 +222,14 @@ class DeviceTracker(object):
update_config(self.hass.config.path(YAML_DEVICES), dev_id, device) update_config(self.hass.config.path(YAML_DEVICES), dev_id, device)
def setup_group(self): def setup_group(self):
""" Initializes group for all tracked devices. """ """Initialize group for all tracked devices."""
entity_ids = (dev.entity_id for dev in self.devices.values() entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track) if dev.track)
self.group = group.Group( self.group = group.Group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False) self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
def update_stale(self, now): def update_stale(self, now):
""" Update stale devices. """ """Update stale devices."""
with self.lock: with self.lock:
for device in self.devices.values(): for device in self.devices.values():
if (device.track and device.last_update_home and if (device.track and device.last_update_home and
@ -242,7 +238,7 @@ class DeviceTracker(object):
class Device(Entity): class Device(Entity):
""" Tracked device. """ """Represent a tracked device."""
host_name = None host_name = None
location_name = None location_name = None
@ -251,12 +247,13 @@ class Device(Entity):
last_seen = None last_seen = None
battery = None battery = None
# Track if the last update of this device was HOME # Track if the last update of this device was HOME.
last_update_home = False last_update_home = False
_state = STATE_NOT_HOME _state = STATE_NOT_HOME
def __init__(self, hass, consider_home, home_range, track, dev_id, mac, def __init__(self, hass, consider_home, home_range, track, dev_id, mac,
name=None, picture=None, away_hide=False): name=None, picture=None, away_hide=False):
"""Initialize a device."""
self.hass = hass self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id) self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@ -282,29 +279,29 @@ class Device(Entity):
@property @property
def gps_home(self): def gps_home(self):
""" Return if device is within range of home. """ """Return if device is within range of home."""
distance = max( distance = max(
0, self.hass.config.distance(*self.gps) - self.gps_accuracy) 0, self.hass.config.distance(*self.gps) - self.gps_accuracy)
return self.gps is not None and distance <= self.home_range return self.gps is not None and distance <= self.home_range
@property @property
def name(self): def name(self):
""" Returns the name of the entity. """ """Return the name of the entity."""
return self.config_name or self.host_name or DEVICE_DEFAULT_NAME return self.config_name or self.host_name or DEVICE_DEFAULT_NAME
@property @property
def state(self): def state(self):
""" State of the device. """ """Return the state of the device."""
return self._state return self._state
@property @property
def entity_picture(self): def entity_picture(self):
"""Picture of the device.""" """Return the picture of the device."""
return self.config_picture return self.config_picture
@property @property
def state_attributes(self): def state_attributes(self):
""" Device state attributes. """ """Return the device state attributes."""
attr = {} attr = {}
if self.gps: if self.gps:
@ -319,12 +316,12 @@ class Device(Entity):
@property @property
def hidden(self): def hidden(self):
""" If device should be hidden. """ """If device should be hidden."""
return self.away_hide and self.state != STATE_HOME return self.away_hide and self.state != STATE_HOME
def seen(self, host_name=None, location_name=None, gps=None, def seen(self, host_name=None, location_name=None, gps=None,
gps_accuracy=0, battery=None): gps_accuracy=0, battery=None):
""" Mark the device as seen. """ """Mark the device as seen."""
self.last_seen = dt_util.utcnow() self.last_seen = dt_util.utcnow()
self.host_name = host_name self.host_name = host_name
self.location_name = location_name self.location_name = location_name
@ -342,12 +339,12 @@ class Device(Entity):
self.update() self.update()
def stale(self, now=None): def stale(self, now=None):
""" Return if device state is stale. """ """Return if device state is stale."""
return self.last_seen and \ return self.last_seen and \
(now or dt_util.utcnow()) - self.last_seen > self.consider_home (now or dt_util.utcnow()) - self.last_seen > self.consider_home
def update(self): def update(self):
""" Update state of entity. """ """Update state of entity."""
if not self.last_seen: if not self.last_seen:
return return
elif self.location_name: elif self.location_name:
@ -370,23 +367,8 @@ class Device(Entity):
self.last_update_home = True self.last_update_home = True
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).lower(),
used_ids)
used_ids.add(dev_id)
device = Device(None, None, None, row['track'] == '1', dev_id,
row['device'], row['name'], row['picture'])
update_config(yaml_path, dev_id, device)
return True
def load_config(path, hass, consider_home, home_range): def load_config(path, hass, consider_home, home_range):
""" Load devices from YAML config file. """ """Load devices from YAML configuration file."""
if not os.path.isfile(path): if not os.path.isfile(path):
return [] return []
return [ return [
@ -398,7 +380,7 @@ def load_config(path, hass, consider_home, home_range):
def setup_scanner_platform(hass, config, scanner, see_device): def setup_scanner_platform(hass, config, scanner, see_device):
""" Helper method to connect scanner-based platform to device tracker. """ """Helper method to connect scanner-based platform to device tracker."""
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int, interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
DEFAULT_SCAN_INTERVAL) DEFAULT_SCAN_INTERVAL)
@ -406,7 +388,7 @@ def setup_scanner_platform(hass, config, scanner, see_device):
seen = set() seen = set()
def device_tracker_scan(now): def device_tracker_scan(now):
""" Called when interval matches. """ """Called when interval matches."""
for mac in scanner.scan_devices(): for mac in scanner.scan_devices():
if mac in seen: if mac in seen:
host_name = None host_name = None
@ -422,7 +404,7 @@ def setup_scanner_platform(hass, config, scanner, see_device):
def update_config(path, dev_id, device): def update_config(path, dev_id, device):
""" Add device to YAML config file. """ """Add device to YAML configuration file."""
with open(path, 'a') as out: with open(path, 'a') as out:
out.write('\n') out.write('\n')
out.write('{}:\n'.format(device.dev_id)) out.write('{}:\n'.format(device.dev_id))

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.actiontec Support for Actiontec MI424WR (Verizon FIOS) routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning an Actiontec MI424WR
(Verizon FIOS) router for device presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.actiontec/ https://home-assistant.io/components/device_tracker.actiontec/
@ -20,7 +17,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -34,7 +31,7 @@ _LEASES_REGEX = re.compile(
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns an Actiontec scanner. """ """Validate the configuration and return an Actiontec scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
@ -46,12 +43,10 @@ Device = namedtuple("Device", ["mac", "ip", "last_update"])
class ActiontecDeviceScanner(object): class ActiontecDeviceScanner(object):
""" """This class queries a an actiontec router for connected devices."""
This class queries a an actiontec router for connected devices.
Adapted from DD-WRT scanner.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
@ -62,15 +57,12 @@ class ActiontecDeviceScanner(object):
_LOGGER.info("actiontec scanner initialized") _LOGGER.info("actiontec scanner initialized")
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() 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): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
if not self.last_results: if not self.last_results:
return None return None
for client in self.last_results: for client in self.last_results:
@ -80,9 +72,9 @@ class ActiontecDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the router is up to date.
Ensures the information from the Actiontec MI424WR router is up
to date. Returns boolean if scanning successful. Return boolean if scanning successful.
""" """
_LOGGER.info("Scanning") _LOGGER.info("Scanning")
if not self.success_init: if not self.success_init:
@ -100,7 +92,7 @@ class ActiontecDeviceScanner(object):
return True return True
def get_actiontec_data(self): def get_actiontec_data(self):
""" Retrieve data from Actiontec MI424WR and return parsed result. """ """Retrieve data from Actiontec MI424WR and return parsed result."""
try: try:
telnet = telnetlib.Telnet(self.host) telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'Username: ') telnet.read_until(b'Username: ')

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.aruba Support for Aruba Access Points.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Aruba Access Point for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.aruba/ https://home-assistant.io/components/device_tracker.aruba/
@ -31,7 +28,7 @@ _DEVICES_REGEX = re.compile(
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Aruba scanner. """ """Validate the configuration and return a Aruba scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
@ -43,9 +40,10 @@ def get_scanner(hass, config):
class ArubaDeviceScanner(object): class ArubaDeviceScanner(object):
""" This class queries a Aruba Acces Point for connected devices. """ """This class queries a Aruba Access Point for connected devices."""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
@ -54,20 +52,17 @@ class ArubaDeviceScanner(object):
self.last_results = {} self.last_results = {}
# Test the router is accessible # Test the router is accessible.
data = self.get_aruba_data() data = self.get_aruba_data()
self.success_init = data is not None self.success_init = data is not None
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info() 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): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
if not self.last_results: if not self.last_results:
return None return None
for client in self.last_results: for client in self.last_results:
@ -77,9 +72,9 @@ class ArubaDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the Aruba Access Point is up to date.
Ensures the information from the Aruba Access Point is up to date.
Returns boolean if scanning successful. Return boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:
return False return False
@ -93,8 +88,7 @@ class ArubaDeviceScanner(object):
return True return True
def get_aruba_data(self): def get_aruba_data(self):
""" Retrieve data from Aruba Access Point and return parsed result. """ """Retrieve data from Aruba Access Point and return parsed result."""
import pexpect import pexpect
connect = "ssh {}@{}" connect = "ssh {}@{}"
ssh = pexpect.spawn(connect.format(self.username, self.host)) ssh = pexpect.spawn(connect.format(self.username, self.host))

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.asuswrt Support for ASUSWRT routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a ASUSWRT router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.asuswrt/ https://home-assistant.io/components/device_tracker.asuswrt/
@ -18,7 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -39,7 +36,7 @@ _IP_NEIGH_REGEX = re.compile(
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns an ASUS-WRT scanner. """ """Validate the configuration and return an ASUS-WRT scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
@ -51,12 +48,10 @@ def get_scanner(hass, config):
class AsusWrtDeviceScanner(object): 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.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.username = str(config[CONF_USERNAME]) self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD]) self.password = str(config[CONF_PASSWORD])
@ -65,20 +60,17 @@ class AsusWrtDeviceScanner(object):
self.last_results = {} self.last_results = {}
# Test the router is accessible # Test the router is accessible.
data = self.get_asuswrt_data() data = self.get_asuswrt_data()
self.success_init = data is not None self.success_init = data is not None
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info() 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): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
if not self.last_results: if not self.last_results:
return None return None
for client in self.last_results: for client in self.last_results:
@ -88,9 +80,9 @@ class AsusWrtDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the ASUSWRT router is up to date.
Ensures the information from the ASUSWRT router is up to date.
Returns boolean if scanning successful. Return boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:
return False return False
@ -109,7 +101,7 @@ class AsusWrtDeviceScanner(object):
return True return True
def get_asuswrt_data(self): def get_asuswrt_data(self):
""" Retrieve data from ASUSWRT and return parsed result. """ """Retrieve data from ASUSWRT and return parsed result."""
try: try:
telnet = telnetlib.Telnet(self.host) telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'login: ') telnet.read_until(b'login: ')
@ -138,9 +130,8 @@ class AsusWrtDeviceScanner(object):
_LOGGER.warning("Could not parse lease row: %s", lease) _LOGGER.warning("Could not parse lease row: %s", lease)
continue continue
# For leases where the client doesn't set a hostname, ensure # For leases where the client doesn't set a hostname, ensure it is
# it is blank and not '*', which breaks the entity_id down # blank and not '*', which breaks the entity_id down the line.
# the line
host = match.group('host') host = match.group('host')
if host == '*': if host == '*':
host = '' host = ''

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.ddwrt Support for DD-WRT routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a DD-WRT router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ddwrt/ https://home-assistant.io/components/device_tracker.ddwrt/
@ -19,7 +16,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -30,7 +27,7 @@ _MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a DD-WRT scanner. """ """Validate the configuration and return a DD-WRT scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
@ -43,12 +40,10 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class DdWrtDeviceScanner(object): 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.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
@ -65,19 +60,15 @@ class DdWrtDeviceScanner(object):
self.success_init = data is not None self.success_init = data is not None
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return self.last_results return self.last_results
def get_device_name(self, device): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
with self.lock: with self.lock:
# if not initialised and not already scanned and not found # If not initialised and not already scanned and not found.
if device not in self.mac2name: if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host) url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url) data = self.get_ddwrt_data(url)
@ -90,15 +81,15 @@ class DdWrtDeviceScanner(object):
if not dhcp_leases: if not dhcp_leases:
return None return None
# remove leading and trailing single quotes # Remove leading and trailing single quotes.
cleaned_str = dhcp_leases.strip().strip('"') cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","') elements = cleaned_str.split('","')
num_clients = int(len(elements)/5) num_clients = int(len(elements)/5)
self.mac2name = {} self.mac2name = {}
for idx in range(0, num_clients): for idx in range(0, num_clients):
# this is stupid but the data is a single array # This is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC # every 5 elements represents one hosts, the MAC
# is the third element and the name is the first # is the third element and the name is the first.
mac_index = (idx * 5) + 2 mac_index = (idx * 5) + 2
if mac_index < len(elements): if mac_index < len(elements):
mac = elements[mac_index] mac = elements[mac_index]
@ -108,9 +99,9 @@ class DdWrtDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the DD-WRT router is up to date.
Ensures the information from the DD-WRT router is up to date.
Returns boolean if scanning successful. Return boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:
return False return False
@ -135,7 +126,7 @@ class DdWrtDeviceScanner(object):
# regex's out values so I guess I have to do the same, # regex's out values so I guess I have to do the same,
# LAME!!! # LAME!!!
# remove leading and trailing single quotes # Remove leading and trailing single quotes.
clean_str = active_clients.strip().strip("'") clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','") elements = clean_str.split("','")
@ -145,7 +136,7 @@ class DdWrtDeviceScanner(object):
return True return True
def get_ddwrt_data(self, url): 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: try:
response = requests.get( response = requests.get(
url, url,
@ -167,7 +158,7 @@ class DdWrtDeviceScanner(object):
def _parse_ddwrt_response(data_str): def _parse_ddwrt_response(data_str):
""" Parse the DD-WRT data format. """ """Parse the DD-WRT data format."""
return { return {
key: val for key, val in _DDWRT_DATA_REGEX key: val for key, val in _DDWRT_DATA_REGEX
.findall(data_str)} .findall(data_str)}

View File

@ -1,25 +1,17 @@
""" """Demo platform for the device tracker."""
homeassistant.components.device_tracker.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform for the device tracker.
device_tracker:
platform: demo
"""
import random import random
from homeassistant.components.device_tracker import DOMAIN from homeassistant.components.device_tracker import DOMAIN
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
""" Set up a demo tracker. """ """Setup the demo tracker."""
def offset(): def offset():
""" Return random offset. """ """Return random offset."""
return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1)) return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1))
def random_see(dev_id, name): def random_see(dev_id, name):
""" Randomize a sighting. """ """Randomize a sighting."""
see( see(
dev_id=dev_id, dev_id=dev_id,
host_name=name, host_name=name,
@ -30,7 +22,7 @@ def setup_scanner(hass, config, see):
) )
def observe(call=None): def observe(call=None):
""" Observe three entities. """ """Observe three entities."""
random_see('demo_paulus', 'Paulus') random_see('demo_paulus', 'Paulus')
random_see('demo_anne_therese', 'Anne Therese') random_see('demo_anne_therese', 'Anne Therese')

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.fritz Support for FRITZ!Box routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a FRITZ!Box router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.fritz/ https://home-assistant.io/components/device_tracker.fritz/
@ -17,15 +14,14 @@ from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.4.6'] REQUIREMENTS = ['fritzconnection==0.4.6']
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# noinspection PyUnusedLocal
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns FritzBoxScanner. """ """Validate the configuration and return FritzBoxScanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: []}, {DOMAIN: []},
_LOGGER): _LOGGER):
@ -37,22 +33,12 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class FritzBoxScanner(object): class FritzBoxScanner(object):
""" """This class queries a FRITZ!Box router."""
This class queries a FRITZ!Box router. It is using the
fritzconnection library for communication with the router.
The API description can be found under:
https://pypi.python.org/pypi/fritzconnection/0.4.6
This scanner retrieves the list of known hosts and checks their
corresponding states (on, or off).
Due to a bug of the fritzbox api (router side) it is not possible
to track more than 16 hosts.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.last_results = [] self.last_results = []
self.host = '169.254.1.1' # This IP is valid for all fritzboxes self.host = '169.254.1.1' # This IP is valid for all FRITZ!Box router.
self.username = 'admin' self.username = 'admin'
self.password = '' self.password = ''
self.success_init = True self.success_init = True
@ -68,7 +54,7 @@ class FritzBoxScanner(object):
if CONF_PASSWORD in config.keys(): if CONF_PASSWORD in config.keys():
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
# Establish a connection to the FRITZ!Box # Establish a connection to the FRITZ!Box.
try: try:
self.fritz_box = fc.FritzHosts(address=self.host, self.fritz_box = fc.FritzHosts(address=self.host,
user=self.username, user=self.username,
@ -77,7 +63,7 @@ class FritzBoxScanner(object):
self.fritz_box = None self.fritz_box = None
# At this point it is difficult to tell if a connection is established. # At this point it is difficult to tell if a connection is established.
# So just check for null objects ... # So just check for null objects.
if self.fritz_box is None or not self.fritz_box.modelname: if self.fritz_box is None or not self.fritz_box.modelname:
self.success_init = False self.success_init = False
@ -90,7 +76,7 @@ class FritzBoxScanner(object):
"with IP: %s", self.host) "with IP: %s", self.host)
def scan_devices(self): def scan_devices(self):
""" Scan for new devices and return a list of found device ids. """ """Scan for new devices and return a list of found device ids."""
self._update_info() self._update_info()
active_hosts = [] active_hosts = []
for known_host in self.last_results: for known_host in self.last_results:
@ -99,7 +85,7 @@ class FritzBoxScanner(object):
return active_hosts return active_hosts
def get_device_name(self, mac): def get_device_name(self, mac):
""" Returns the name of the given device or None if is not known. """ """Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac)["NewHostName"] ret = self.fritz_box.get_specific_host_entry(mac)["NewHostName"]
if ret == {}: if ret == {}:
return None return None
@ -107,7 +93,7 @@ class FritzBoxScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" Retrieves latest information from the FRITZ!Box. """ """Retrieve latest information from the FRITZ!Box."""
if not self.success_init: if not self.success_init:
return False return False

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.device_tracker.icloud Support for iCloud connected devices.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning iCloud devices.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.icloud/ https://home-assistant.io/components/device_tracker.icloud/
@ -21,12 +19,12 @@ DEFAULT_INTERVAL = 8
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
""" Set up the iCloud Scanner. """ """Setup the iCloud Scanner."""
from pyicloud import PyiCloudService from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudFailedLoginException from pyicloud.exceptions import PyiCloudFailedLoginException
from pyicloud.exceptions import PyiCloudNoDevicesException from pyicloud.exceptions import PyiCloudNoDevicesException
# Get the username and password from the configuration # Get the username and password from the configuration.
username = config.get(CONF_USERNAME) username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
@ -45,14 +43,14 @@ def setup_scanner(hass, config, see):
return False return False
def keep_alive(now): def keep_alive(now):
""" Keeps authenticating iCloud connection. """ """Keep authenticating iCloud connection."""
api.authenticate() api.authenticate()
_LOGGER.info("Authenticate against iCloud") _LOGGER.info("Authenticate against iCloud")
track_utc_time_change(hass, keep_alive, second=0) track_utc_time_change(hass, keep_alive, second=0)
def update_icloud(now): def update_icloud(now):
""" Authenticate against iCloud and scan for devices. """ """Authenticate against iCloud and scan for devices."""
try: try:
# The session timeouts if we are not using it so we # The session timeouts if we are not using it so we
# have to re-authenticate. This will send an email. # have to re-authenticate. This will send an email.

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.device_tracker.locative Support for the Locative platform.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Locative platform for the device tracker.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.locative/ https://home-assistant.io/components/device_tracker.locative/
@ -20,12 +18,10 @@ URL_API_LOCATIVE_ENDPOINT = "/api/locative"
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
""" Set up an endpoint for the Locative app. """ """Setup an endpoint for the Locative application."""
# POST would be semantically better, but that currently does not work # POST would be semantically better, but that currently does not work
# since Locative sends the data as key1=value1&key2=value2 # since Locative sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there. # in the request body, while Home Assistant expects json there.
hass.http.register_path( hass.http.register_path(
'GET', URL_API_LOCATIVE_ENDPOINT, 'GET', URL_API_LOCATIVE_ENDPOINT,
partial(_handle_get_api_locative, hass, see)) partial(_handle_get_api_locative, hass, see))
@ -34,8 +30,7 @@ def setup_scanner(hass, config, see):
def _handle_get_api_locative(hass, see, handler, path_match, data): def _handle_get_api_locative(hass, see, handler, path_match, data):
""" Locative message received. """ """Locative message received."""
if not _check_data(handler, data): if not _check_data(handler, data):
return return
@ -76,6 +71,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data):
def _check_data(handler, data): def _check_data(handler, data):
"""Check the data."""
if 'latitude' not in data or 'longitude' not in data: if 'latitude' not in data or 'longitude' not in data:
handler.write_text("Latitude and longitude not specified.", handler.write_text("Latitude and longitude not specified.",
HTTP_UNPROCESSABLE_ENTITY) HTTP_UNPROCESSABLE_ENTITY)

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.luci Support for OpenWRT (luci) routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.luci/ https://home-assistant.io/components/device_tracker.luci/
@ -20,14 +17,14 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """ """Validate the configuration and return a Luci scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
@ -40,20 +37,13 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class LuciDeviceScanner(object): 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 Adapted from Tomato scanner.
for this to work on the router.
The API is described here:
http://luci.subsignal.org/trac/wiki/Documentation/JsonRpcHowTo
(Currently, we do only wifi iwscan, and no DHCP lease access.)
""" """
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST] host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD] username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@ -70,17 +60,12 @@ class LuciDeviceScanner(object):
self.success_init = self.token is not None self.success_init = self.token is not None
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return self.last_results return self.last_results
def get_device_name(self, device): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
with self.lock: with self.lock:
if self.mac2name is None: if self.mac2name is None:
url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host) url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host)
@ -100,8 +85,8 @@ class LuciDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the Luci router is up to date.
Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful. Returns boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:
@ -127,7 +112,7 @@ class LuciDeviceScanner(object):
def _req_json_rpc(url, method, *args, **kwargs): def _req_json_rpc(url, method, *args, **kwargs):
""" Perform one JSON RPC operation. """ """Perform one JSON RPC operation."""
data = json.dumps({'method': method, 'params': args}) data = json.dumps({'method': method, 'params': args})
try: try:
res = requests.post(url, data=data, timeout=5, **kwargs) res = requests.post(url, data=data, timeout=5, **kwargs)
@ -157,6 +142,6 @@ def _req_json_rpc(url, method, *args, **kwargs):
def _get_token(host, username, password): 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) url = 'http://{}/cgi-bin/luci/rpc/auth'.format(host)
return _req_json_rpc(url, 'login', username, password) return _req_json_rpc(url, 'login', username, password)

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.device_tracker.mqtt Support for tracking MQTT enabled devices.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MQTT platform for the device tracker.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt/ https://home-assistant.io/components/device_tracker.mqtt/
@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
""" Set up a MQTT tracker. """ """Setup the MQTT tracker."""
devices = config.get(CONF_DEVICES) devices = config.get(CONF_DEVICES)
qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS) qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS)
@ -34,7 +32,7 @@ def setup_scanner(hass, config, see):
dev_id_lookup = {} dev_id_lookup = {}
def device_tracker_message_received(topic, payload, qos): def device_tracker_message_received(topic, payload, qos):
""" MQTT message received. """ """MQTT message received."""
see(dev_id=dev_id_lookup[topic], location_name=payload) see(dev_id=dev_id_lookup[topic], location_name=payload)
for dev_id, topic in devices.items(): for dev_id, topic in devices.items():

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.netgear Support for Netgear routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Netgear router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.netgear/ https://home-assistant.io/components/device_tracker.netgear/
@ -15,7 +12,7 @@ from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,7 +20,7 @@ REQUIREMENTS = ['pynetgear==0.3.2']
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Netgear scanner. """ """Validate the configuration and returns a Netgear scanner."""
info = config[DOMAIN] info = config[DOMAIN]
host = info.get(CONF_HOST) host = info.get(CONF_HOST)
username = info.get(CONF_USERNAME) username = info.get(CONF_USERNAME)
@ -39,9 +36,10 @@ def get_scanner(hass, config):
class NetgearDeviceScanner(object): class NetgearDeviceScanner(object):
""" This class queries a Netgear wireless router using the SOAP-API. """ """Queries a Netgear wireless router using the SOAP-API."""
def __init__(self, host, username, password): def __init__(self, host, username, password):
"""Initialize the scanner."""
import pynetgear import pynetgear
self.last_results = [] self.last_results = []
@ -66,15 +64,13 @@ class NetgearDeviceScanner(object):
_LOGGER.error("Failed to Login") _LOGGER.error("Failed to Login")
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return (device.mac for device in self.last_results) return (device.mac for device in self.last_results)
def get_device_name(self, mac): def get_device_name(self, mac):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
try: try:
return next(device.name for device in self.last_results return next(device.name for device in self.last_results
if device.mac == mac) if device.mac == mac)
@ -83,8 +79,8 @@ class NetgearDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Retrieve latest information from the Netgear router.
Retrieves latest information from the Netgear router.
Returns boolean if scanning successful. Returns boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.device_tracker.nmap Support for scanning a network with nmap.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a network with nmap.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.nmap_scanner/ https://home-assistant.io/components/device_tracker.nmap_scanner/
@ -30,7 +28,7 @@ REQUIREMENTS = ['python-nmap==0.4.3']
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """ """Validate the configuration and return a Nmap scanner."""
if not validate_config(config, {DOMAIN: [CONF_HOSTS]}, if not validate_config(config, {DOMAIN: [CONF_HOSTS]},
_LOGGER): _LOGGER):
return None return None
@ -43,7 +41,7 @@ Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
def _arp(ip_address): def _arp(ip_address):
""" Get the MAC address for a given IP. """ """Get the MAC address for a given IP."""
cmd = ['arp', '-n', ip_address] cmd = ['arp', '-n', ip_address]
arp = subprocess.Popen(cmd, stdout=subprocess.PIPE) arp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
out, _ = arp.communicate() out, _ = arp.communicate()
@ -55,9 +53,10 @@ def _arp(ip_address):
class NmapDeviceScanner(object): class NmapDeviceScanner(object):
""" This class scans for devices using nmap. """ """This class scans for devices using nmap."""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.last_results = [] self.last_results = []
self.hosts = config[CONF_HOSTS] self.hosts = config[CONF_HOSTS]
@ -68,17 +67,13 @@ class NmapDeviceScanner(object):
_LOGGER.info("nmap scanner initialized") _LOGGER.info("nmap scanner initialized")
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return [device.mac for device in self.last_results] return [device.mac for device in self.last_results]
def get_device_name(self, mac): def get_device_name(self, mac):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
filter_named = [device.name for device in self.last_results filter_named = [device.name for device in self.last_results
if device.mac == mac] if device.mac == mac]
@ -89,8 +84,8 @@ class NmapDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Scan the network for devices.
Scans the network for devices.
Returns boolean if scanning successful. Returns boolean if scanning successful.
""" """
_LOGGER.info("Scanning") _LOGGER.info("Scanning")

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.device_tracker.owntracks Support the OwnTracks platform.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OwnTracks platform for the device tracker.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks/ https://home-assistant.io/components/device_tracker.owntracks/
@ -28,13 +26,15 @@ _LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock() LOCK = threading.Lock()
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
def setup_scanner(hass, config, see): def setup_scanner(hass, config, see):
""" Set up an OwnTracks tracker. """ """Setup an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
def owntracks_location_update(topic, payload, qos): def owntracks_location_update(topic, payload, qos):
""" MQTT message received. """ """MQTT message received."""
# Docs on available data: # Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation # http://owntracks.org/booklet/tech/json/#_typelocation
try: try:
@ -45,7 +45,9 @@ def setup_scanner(hass, config, see):
'Unable to parse payload as JSON: %s', payload) 'Unable to parse payload as JSON: %s', payload)
return return
if not isinstance(data, dict) or data.get('_type') != 'location': if (not isinstance(data, dict) or data.get('_type') != 'location') or (
'acc' in data and max_gps_accuracy is not None and data[
'acc'] > max_gps_accuracy):
return return
dev_id, kwargs = _parse_see_args(topic, data) dev_id, kwargs = _parse_see_args(topic, data)
@ -63,8 +65,7 @@ def setup_scanner(hass, config, see):
def owntracks_event_update(topic, payload, qos): def owntracks_event_update(topic, payload, qos):
# pylint: disable=too-many-branches, too-many-statements # pylint: disable=too-many-branches, too-many-statements
""" MQTT event (geofences) received. """ """MQTT event (geofences) received."""
# Docs on available data: # Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition # http://owntracks.org/booklet/tech/json/#_typetransition
try: try:
@ -124,12 +125,20 @@ def setup_scanner(hass, config, see):
kwargs['location_name'] = new_region kwargs['location_name'] = new_region
_set_gps_from_zone(kwargs, zone) _set_gps_from_zone(kwargs, zone)
_LOGGER.info("Exit to %s", new_region) _LOGGER.info("Exit to %s", new_region)
see(**kwargs)
see_beacons(dev_id, kwargs)
else: else:
_LOGGER.info("Exit to GPS") _LOGGER.info("Exit to GPS")
# Check for GPS accuracy
if not ('acc' in data and
max_gps_accuracy is not None and
data['acc'] > max_gps_accuracy):
see(**kwargs) see(**kwargs)
see_beacons(dev_id, kwargs) see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Inaccurate GPS reported")
beacons = MOBILE_BEACONS_ACTIVE[dev_id] beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location in beacons: if location in beacons:
@ -143,8 +152,7 @@ def setup_scanner(hass, config, see):
return return
def see_beacons(dev_id, kwargs_param): def see_beacons(dev_id, kwargs_param):
""" Set active beacons to the current location """ """Set active beacons to the current location."""
kwargs = kwargs_param.copy() kwargs = kwargs_param.copy()
# the battery state applies to the tracking device, not the beacon # the battery state applies to the tracking device, not the beacon
kwargs.pop('battery', None) kwargs.pop('battery', None)
@ -154,16 +162,13 @@ def setup_scanner(hass, config, see):
see(**kwargs) see(**kwargs)
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1) mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1) mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
return True return True
def _parse_see_args(topic, data): def _parse_see_args(topic, data):
""" Parse the OwnTracks location parameters, """Parse the OwnTracks location parameters, into the format see expects."""
into the format see expects. """
parts = topic.split('/') parts = topic.split('/')
dev_id = '{}_{}'.format(parts[1], parts[2]) dev_id = '{}_{}'.format(parts[1], parts[2])
host_name = parts[1] host_name = parts[1]
@ -180,8 +185,7 @@ def _parse_see_args(topic, data):
def _set_gps_from_zone(kwargs, zone): def _set_gps_from_zone(kwargs, zone):
""" Set the see parameters from the zone parameters """ """Set the see parameters from the zone parameters."""
if zone is not None: if zone is not None:
kwargs['gps'] = ( kwargs['gps'] = (
zone.attributes['latitude'], zone.attributes['latitude'],

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.snmp Support for fetching WiFi associations through SNMP.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports fetching WiFi associations
through SNMP.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.snmp/ https://home-assistant.io/components/device_tracker.snmp/
@ -17,7 +14,7 @@ from homeassistant.const import CONF_HOST
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -29,7 +26,7 @@ CONF_BASEOID = "baseoid"
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns an snmp scanner """ """Validate the configuration and return an snmp scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]}, {DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]},
_LOGGER): _LOGGER):
@ -41,10 +38,10 @@ def get_scanner(hass, config):
class SnmpScanner(object): class SnmpScanner(object):
""" """Queries any SNMP capable Access Point for connected devices."""
This class queries any SNMP capable Acces Point for connected devices.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
from pysnmp.entity.rfc3413.oneliner import cmdgen from pysnmp.entity.rfc3413.oneliner import cmdgen
self.snmp = cmdgen.CommandGenerator() self.snmp = cmdgen.CommandGenerator()
@ -61,25 +58,23 @@ class SnmpScanner(object):
self.success_init = data is not None self.success_init = data is not None
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info() self._update_info()
return [client['mac'] for client in self.last_results] return [client['mac'] for client in self.last_results
if client.get('mac')]
# Supressing no-self-use warning # Supressing no-self-use warning
# pylint: disable=R0201 # pylint: disable=R0201
def get_device_name(self, device): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
# We have no names # We have no names
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the WAP is up to date.
Ensures the information from the WAP is up to date.
Returns boolean if scanning successful. Return boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:
return False return False
@ -93,8 +88,7 @@ class SnmpScanner(object):
return True return True
def get_snmp_data(self): def get_snmp_data(self):
""" Fetch mac addresses from WAP via SNMP. """ """Fetch MAC addresses from WAP via SNMP."""
devices = [] devices = []
errindication, errstatus, errindex, restable = self.snmp.nextCmd( errindication, errstatus, errindex, restable = self.snmp.nextCmd(
@ -111,6 +105,7 @@ class SnmpScanner(object):
for resrow in restable: for resrow in restable:
for _, val in resrow: for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8') mac = binascii.hexlify(val.asOctets()).decode('utf-8')
_LOGGER.debug('Found mac %s', mac)
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)]) mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac}) devices.append({'mac': mac})
return devices return devices

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.thomson Support for THOMSON routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a THOMSON router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.thomson/ https://home-assistant.io/components/device_tracker.thomson/
@ -18,7 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -35,7 +32,7 @@ _DEVICES_REGEX = re.compile(
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a THOMSON scanner. """ """Validate the configuration and return a THOMSON scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
@ -47,12 +44,10 @@ def get_scanner(hass, config):
class ThomsonDeviceScanner(object): class ThomsonDeviceScanner(object):
""" """This class queries a router running THOMSON firmware."""
This class queries a router running THOMSON firmware
for connected devices. Adapted from ASUSWRT scanner.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
@ -61,20 +56,17 @@ class ThomsonDeviceScanner(object):
self.last_results = {} self.last_results = {}
# Test the router is accessible # Test the router is accessible.
data = self.get_thomson_data() data = self.get_thomson_data()
self.success_init = data is not None self.success_init = data is not None
def scan_devices(self): def scan_devices(self):
""" Scans for new devices and return a """Scan for new devices and return a list with found device IDs."""
list containing found device ids. """
self._update_info() 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): def get_device_name(self, device):
""" Returns the name of the given device """Return the name of the given device or None if we don't know."""
or None if we don't know. """
if not self.last_results: if not self.last_results:
return None return None
for client in self.last_results: for client in self.last_results:
@ -84,9 +76,9 @@ class ThomsonDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the THOMSON router is up to date.
Ensures the information from the THOMSON router is up to date.
Returns boolean if scanning successful. Return boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:
return False return False
@ -97,14 +89,14 @@ class ThomsonDeviceScanner(object):
if not data: if not data:
return False return False
# flag C stands for CONNECTED # Flag C stands for CONNECTED
active_clients = [client for client in data.values() if active_clients = [client for client in data.values() if
client['status'].find('C') != -1] client['status'].find('C') != -1]
self.last_results = active_clients self.last_results = active_clients
return True return True
def get_thomson_data(self): def get_thomson_data(self):
""" Retrieve data from THOMSON and return parsed result. """ """Retrieve data from THOMSON and return parsed result."""
try: try:
telnet = telnetlib.Telnet(self.host) telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'Username : ') telnet.read_until(b'Username : ')

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.tomato Support for Tomato routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Tomato router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tomato/ https://home-assistant.io/components/device_tracker.tomato/
@ -20,7 +17,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
CONF_HTTP_ID = "http_id" CONF_HTTP_ID = "http_id"
@ -29,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Tomato scanner. """ """Validate the configuration and returns a Tomato scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, {DOMAIN: [CONF_HOST, CONF_USERNAME,
CONF_PASSWORD, CONF_HTTP_ID]}, CONF_PASSWORD, CONF_HTTP_ID]},
@ -40,14 +37,10 @@ def get_scanner(hass, config):
class TomatoDeviceScanner(object): class TomatoDeviceScanner(object):
""" This class queries a wireless router running Tomato firmware """This class queries a wireless router running Tomato firmware."""
for connected devices.
A description of the Tomato API can be found on
http://paulusschoutsen.nl/blog/2013/10/tomato-api-documentation/
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
host, http_id = config[CONF_HOST], config[CONF_HTTP_ID] host, http_id = config[CONF_HOST], config[CONF_HTTP_ID]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD] username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@ -68,16 +61,13 @@ class TomatoDeviceScanner(object):
self.success_init = self._update_tomato_info() self.success_init = self._update_tomato_info()
def scan_devices(self): def scan_devices(self):
""" Scans for new devices and return a """Scan for new devices and return a list with found device IDs."""
list containing found device ids. """
self._update_tomato_info() self._update_tomato_info()
return [item[1] for item in self.last_results['wldev']] return [item[1] for item in self.last_results['wldev']]
def get_device_name(self, device): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
filter_named = [item[0] for item in self.last_results['dhcpd_lease'] filter_named = [item[0] for item in self.last_results['dhcpd_lease']
if item[2] == device] if item[2] == device]
@ -88,19 +78,17 @@ class TomatoDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_tomato_info(self): def _update_tomato_info(self):
""" Ensures the information from the Tomato router is up to date. """Ensure the information from the Tomato router is up to date.
Returns boolean if scanning successful. """
Return boolean if scanning successful.
"""
with self.lock: with self.lock:
self.logger.info("Scanning") self.logger.info("Scanning")
try: try:
response = requests.Session().send(self.req, timeout=3) response = requests.Session().send(self.req, timeout=3)
# Calling and parsing the Tomato api here. We only need the # Calling and parsing the Tomato api here. We only need the
# wldev and dhcpd_lease values. For API description see: # wldev and dhcpd_lease values.
# http://paulusschoutsen.nl/
# blog/2013/10/tomato-api-documentation/
if response.status_code == 200: if response.status_code == 200:
for param, value in \ for param, value in \
@ -109,7 +97,6 @@ class TomatoDeviceScanner(object):
if param == 'wldev' or param == 'dhcpd_lease': if param == 'wldev' or param == 'dhcpd_lease':
self.last_results[param] = \ self.last_results[param] = \
json.loads(value.replace("'", '"')) json.loads(value.replace("'", '"'))
return True return True
elif response.status_code == 401: elif response.status_code == 401:
@ -117,29 +104,25 @@ class TomatoDeviceScanner(object):
self.logger.exception(( self.logger.exception((
"Failed to authenticate, " "Failed to authenticate, "
"please check your username and password")) "please check your username and password"))
return False return False
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
# We get this if we could not connect to the router or # We get this if we could not connect to the router or
# an invalid http_id was supplied # an invalid http_id was supplied.
self.logger.exception(( self.logger.exception((
"Failed to connect to the router" "Failed to connect to the router"
" or invalid http_id supplied")) " or invalid http_id supplied"))
return False return False
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
# We get this if we could not connect to the router or # We get this if we could not connect to the router or
# an invalid http_id was supplied # an invalid http_id was supplied.
self.logger.exception( self.logger.exception(
"Connection to the router timed out") "Connection to the router timed out")
return False return False
except ValueError: except ValueError:
# If json decoder could not parse the response # If JSON decoder could not parse the response.
self.logger.exception( self.logger.exception(
"Failed to parse response from router") "Failed to parse response from router")
return False return False

View File

@ -1,13 +1,11 @@
""" """
homeassistant.components.device_tracker.tplink Support for TP-Link routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a TP-Link router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tplink/ https://home-assistant.io/components/device_tracker.tplink/
""" """
import base64 import base64
import hashlib
import logging import logging
import re import re
import threading import threading
@ -27,12 +25,15 @@ _LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a TP-Link scanner. """ """Validate the configuration and return a TP-Link scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
return None return None
scanner = Tplink4DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = Tplink3DeviceScanner(config[DOMAIN]) scanner = Tplink3DeviceScanner(config[DOMAIN])
if not scanner.success_init: if not scanner.success_init:
@ -45,12 +46,10 @@ def get_scanner(hass, config):
class TplinkDeviceScanner(object): 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.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST] host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD] username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@ -66,29 +65,21 @@ class TplinkDeviceScanner(object):
self.success_init = self._update_info() self.success_init = self._update_info()
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return self.last_results return self.last_results
# pylint: disable=no-self-use # pylint: disable=no-self-use
def get_device_name(self, device): def get_device_name(self, device):
""" """The 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 return None
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the TP-Link router is up to date.
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
Return boolean if scanning successful.
"""
with self.lock: with self.lock:
_LOGGER.info("Loading wireless clients...") _LOGGER.info("Loading wireless clients...")
@ -107,34 +98,24 @@ class TplinkDeviceScanner(object):
class Tplink2DeviceScanner(TplinkDeviceScanner): class Tplink2DeviceScanner(TplinkDeviceScanner):
""" """This class queries a router with newer version of TP-Link firmware."""
This class queries a wireless router running newer version of TP-Link
firmware for connected devices.
"""
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return self.last_results.keys() return self.last_results.keys()
# pylint: disable=no-self-use # pylint: disable=no-self-use
def get_device_name(self, device): def get_device_name(self, device):
""" """The 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) return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the TP-Link router is up to date.
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
Return boolean if scanning successful.
"""
with self.lock: with self.lock:
_LOGGER.info("Loading wireless clients...") _LOGGER.info("Loading wireless clients...")
@ -172,46 +153,36 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
class Tplink3DeviceScanner(TplinkDeviceScanner): class Tplink3DeviceScanner(TplinkDeviceScanner):
""" """This class queries the Archer C9 router with version 150811 or high."""
This class queries the Archer C9 router running version 150811 or higher
of TP-Link firmware for connected devices.
"""
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
self.stok = '' self.stok = ''
self.sysauth = '' self.sysauth = ''
super(Tplink3DeviceScanner, self).__init__(config) super(Tplink3DeviceScanner, self).__init__(config)
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return self.last_results.keys() return self.last_results.keys()
# pylint: disable=no-self-use # pylint: disable=no-self-use
def get_device_name(self, device): def get_device_name(self, device):
""" """The firmware doesn't save the name of the wireless device.
The TP-Link firmware doesn't save the name of the wireless device.
We are forced to use the MAC address as name here. We are forced to use the MAC address as name here.
""" """
return self.last_results.get(device) return self.last_results.get(device)
def _get_auth_tokens(self): def _get_auth_tokens(self):
""" """Retrieve auth tokens from the router."""
Retrieves auth tokens from the router.
"""
_LOGGER.info("Retrieving auth tokens...") _LOGGER.info("Retrieving auth tokens...")
url = 'http://{}/cgi-bin/luci/;stok=/login?form=login' \ url = 'http://{}/cgi-bin/luci/;stok=/login?form=login' \
.format(self.host) .format(self.host)
referer = 'http://{}/webpages/login.html'.format(self.host) referer = 'http://{}/webpages/login.html'.format(self.host)
# if possible implement rsa encryption of password here # If possible implement rsa encryption of password here.
response = requests.post(url, response = requests.post(url,
params={'operation': 'login', params={'operation': 'login',
'username': self.username, 'username': self.username,
@ -232,11 +203,10 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the TP-Link router is up to date.
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
Return boolean if scanning successful.
"""
with self.lock: with self.lock:
if (self.stok == '') or (self.sysauth == ''): if (self.stok == '') or (self.sysauth == ''):
self._get_auth_tokens() self._get_auth_tokens()
@ -281,3 +251,81 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
return True return True
return False return False
class Tplink4DeviceScanner(TplinkDeviceScanner):
"""This class queries an Archer C7 router with TP-Link firmware 150427."""
def __init__(self, config):
"""Initialize the scanner."""
self.credentials = ''
self.token = ''
super(Tplink4DeviceScanner, self).__init__(config)
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
# pylint: disable=no-self-use
def get_device_name(self, device):
"""The firmware doesn't save the name of the wireless device."""
return None
def _get_auth_tokens(self):
"""Retrieve auth tokens from the router."""
_LOGGER.info("Retrieving auth tokens...")
url = 'http://{}/userRpm/LoginRpm.htm?Save=Save'.format(self.host)
# Generate md5 hash of password
password = hashlib.md5(self.password.encode('utf')).hexdigest()
credentials = '{}:{}'.format(self.username, password).encode('utf')
# Encode the credentials to be sent as a cookie.
self.credentials = base64.b64encode(credentials).decode('utf')
# Create the authorization cookie.
cookie = 'Authorization=Basic {}'.format(self.credentials)
response = requests.get(url, headers={'cookie': cookie})
try:
result = re.search(r'window.parent.location.href = '
r'"https?:\/\/.*\/(.*)\/userRpm\/Index.htm";',
response.text)
if not result:
return False
self.token = result.group(1)
return True
except ValueError:
_LOGGER.error("Couldn't fetch auth tokens!")
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful.
"""
with self.lock:
if (self.credentials == '') or (self.token == ''):
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/{}/userRpm/WlanStationRpm.htm' \
.format(self.host, self.token)
referer = 'http://{}'.format(self.host)
cookie = 'Authorization=Basic {}'.format(self.credentials)
page = requests.get(url, headers={
'cookie': cookie,
'referer': referer
})
result = self.parse_macs.findall(page.text)
if not result:
return False
self.last_results = [mac.replace("-", ":") for mac in result]
return True

View File

@ -1,8 +1,5 @@
""" """
homeassistant.components.device_tracker.ubus Support for OpenWRT (ubus) routers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ubus/ https://home-assistant.io/components/device_tracker.ubus/
@ -20,14 +17,14 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """ """Validate the configuration and return an ubus scanner."""
if not validate_config(config, if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER): _LOGGER):
@ -41,23 +38,13 @@ def get_scanner(hass, config):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class UbusDeviceScanner(object): class UbusDeviceScanner(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.
Configure your routers' ubus ACL based on following instructions:
http://wiki.openwrt.org/doc/techref/ubus
Read only access will be fine.
To use this class you have to install rpcd-mod-file package
in your OpenWrt router:
opkg install rpcd-mod-file
Adapted from Tomato scanner.
""" """
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST] host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD] username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
@ -73,17 +60,12 @@ class UbusDeviceScanner(object):
self.success_init = self.session_id is not None self.success_init = self.session_id is not None
def scan_devices(self): def scan_devices(self):
""" """Scan for new devices and return a list with found device IDs."""
Scans for new devices and return a list containing found device ids.
"""
self._update_info() self._update_info()
return self.last_results return self.last_results
def get_device_name(self, device): def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """ """Return the name of the given device or None if we don't know."""
with self.lock: with self.lock:
if self.leasefile is None: if self.leasefile is None:
result = _req_json_rpc(self.url, self.session_id, result = _req_json_rpc(self.url, self.session_id,
@ -112,8 +94,8 @@ class UbusDeviceScanner(object):
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """Ensure the information from the Luci router is up to date.
Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful. Returns boolean if scanning successful.
""" """
if not self.success_init: if not self.success_init:
@ -141,8 +123,7 @@ class UbusDeviceScanner(object):
def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params): def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
""" Perform one JSON RPC operation. """ """Perform one JSON RPC operation."""
data = json.dumps({"jsonrpc": "2.0", data = json.dumps({"jsonrpc": "2.0",
"id": 1, "id": 1,
"method": rpcmethod, "method": rpcmethod,
@ -167,7 +148,7 @@ def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
def _get_session_id(url, username, password): def _get_session_id(url, username, password):
""" Get authentication token for the given host+username+password. """ """Get the authentication token for the given host+username+password."""
res = _req_json_rpc(url, "00000000000000000000000000000000", 'call', res = _req_json_rpc(url, "00000000000000000000000000000000", 'call',
'session', 'login', username=username, 'session', 'login', username=username,
password=password) password=password)

View File

@ -1,7 +1,8 @@
""" """
homeassistant.components.device_tracker.unifi Support for Unifi WAP controllers.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Unifi WAP controller For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.unifi/
""" """
import logging import logging
import urllib import urllib
@ -17,7 +18,7 @@ CONF_PORT = 'port'
def get_scanner(hass, config): def get_scanner(hass, config):
""" Sets up unifi device_tracker """ """Setup Unifi device_tracker."""
from unifi.controller import Controller from unifi.controller import Controller
if not validate_config(config, {DOMAIN: [CONF_USERNAME, if not validate_config(config, {DOMAIN: [CONF_USERNAME,
@ -50,10 +51,12 @@ class UnifiScanner(object):
"""Provide device_tracker support from Unifi WAP client data.""" """Provide device_tracker support from Unifi WAP client data."""
def __init__(self, controller): def __init__(self, controller):
"""Initialize the scanner."""
self._controller = controller self._controller = controller
self._update() self._update()
def _update(self): def _update(self):
"""Get the clients from the device."""
try: try:
clients = self._controller.get_clients() clients = self._controller.get_clients()
except urllib.error.HTTPError as ex: except urllib.error.HTTPError as ex:
@ -63,12 +66,12 @@ class UnifiScanner(object):
self._clients = {client['mac']: client for client in clients} self._clients = {client['mac']: client for client in clients}
def scan_devices(self): def scan_devices(self):
""" Scans for devices. """ """Scan for devices."""
self._update() self._update()
return self._clients.keys() return self._clients.keys()
def get_device_name(self, mac): def get_device_name(self, mac):
""" Returns the name (if known) of the device. """Return the name (if known) of the device.
If a name has been set in Unifi, then return that, else If a name has been set in Unifi, then return that, else
return the hostname if it has been detected. return the hostname if it has been detected.

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED) EVENT_PLATFORM_DISCOVERED)
DOMAIN = "discovery" DOMAIN = "discovery"
REQUIREMENTS = ['netdisco==0.5.2'] REQUIREMENTS = ['netdisco==0.5.4']
SCAN_INTERVAL = 300 # seconds SCAN_INTERVAL = 300 # seconds
@ -55,10 +55,7 @@ def listen(hass, service, callback):
def discover(hass, service, discovered=None, component=None, hass_config=None): def discover(hass, service, discovered=None, component=None, hass_config=None):
"""Fire discovery event. """Fire discovery event. Can ensure a component is loaded."""
Can ensure a component is loaded.
"""
if component is not None: if component is not None:
bootstrap.setup_component(hass, component, hass_config) bootstrap.setup_component(hass, component, hass_config)
@ -73,7 +70,7 @@ def discover(hass, service, discovered=None, component=None, hass_config=None):
def setup(hass, config): def setup(hass, config):
""" Starts a discovery service. """ """Start a discovery service."""
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from netdisco.service import DiscoveryService from netdisco.service import DiscoveryService
@ -84,13 +81,13 @@ def setup(hass, config):
lock = threading.Lock() lock = threading.Lock()
def new_service_listener(service, info): def new_service_listener(service, info):
""" Called when a new service is found. """ """Called when a new service is found."""
with lock: with lock:
logger.info("Found new service: %s %s", service, info) logger.info("Found new service: %s %s", service, info)
component = SERVICE_HANDLERS.get(service) component = SERVICE_HANDLERS.get(service)
# We do not know how to handle this service # We do not know how to handle this service.
if not component: if not component:
return return
@ -105,7 +102,7 @@ def setup(hass, config):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def start_discovery(event): def start_discovery(event):
""" Start discovering. """ """Start discovering."""
netdisco = DiscoveryService(SCAN_INTERVAL) netdisco = DiscoveryService(SCAN_INTERVAL)
netdisco.add_listener(new_service_listener) netdisco.add_listener(new_service_listener)
netdisco.start() netdisco.start()

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.downloader Support for functionality to download files.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to download files.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/downloader/ https://home-assistant.io/components/downloader/
@ -28,8 +26,7 @@ CONF_DOWNLOAD_DIR = 'download_dir'
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def setup(hass, config): def setup(hass, config):
""" Listens for download events to download files. """ """Listen for download events to download files."""
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger): if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
@ -50,14 +47,13 @@ def setup(hass, config):
return False return False
def download_file(service): def download_file(service):
""" Starts thread to download file specified in the url. """ """Start thread to download file specified in the URL."""
if ATTR_URL not in service.data: if ATTR_URL not in service.data:
logger.error("Service called but 'url' parameter not specified.") logger.error("Service called but 'url' parameter not specified.")
return return
def do_download(): def do_download():
""" Downloads the file. """ """Download the file."""
try: try:
url = service.data[ATTR_URL] url = service.data[ATTR_URL]

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.ecobee Support for Ecobee.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ecobee component
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ecobee/ https://home-assistant.io/components/ecobee/
@ -31,12 +29,12 @@ _LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf' ECOBEE_CONFIG_FILE = 'ecobee.conf'
_CONFIGURING = {} _CONFIGURING = {}
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
def request_configuration(network, hass, config): def request_configuration(network, hass, config):
""" Request configuration steps from the user. """ """Request configuration steps from the user."""
configurator = get_component('configurator') configurator = get_component('configurator')
if 'ecobee' in _CONFIGURING: if 'ecobee' in _CONFIGURING:
configurator.notify_errors( configurator.notify_errors(
@ -46,7 +44,7 @@ def request_configuration(network, hass, config):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def ecobee_configuration_callback(callback_data): def ecobee_configuration_callback(callback_data):
""" Actions to do when our configuration callback is called. """ """The actions to do when our configuration callback is called."""
network.request_tokens() network.request_tokens()
network.update() network.update()
setup_ecobee(hass, network, config) setup_ecobee(hass, network, config)
@ -62,7 +60,7 @@ def request_configuration(network, hass, config):
def setup_ecobee(hass, network, config): def setup_ecobee(hass, network, config):
""" Setup Ecobee thermostat. """ """Setup Ecobee thermostat."""
# If ecobee has a PIN then it needs to be configured. # If ecobee has a PIN then it needs to be configured.
if network.pin is not None: if network.pin is not None:
request_configuration(network, hass, config) request_configuration(network, hass, config)
@ -93,22 +91,23 @@ def setup_ecobee(hass, network, config):
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
class EcobeeData(object): class EcobeeData(object):
""" Gets the latest data and update the states. """ """Get the latest data and update the states."""
def __init__(self, config_file): def __init__(self, config_file):
"""Initialize the Ecobee data object."""
from pyecobee import Ecobee from pyecobee import Ecobee
self.ecobee = Ecobee(config_file) self.ecobee = Ecobee(config_file)
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
""" Get the latest data from pyecobee. """ """Get the latest data from pyecobee."""
self.ecobee.update() self.ecobee.update()
_LOGGER.info("ecobee data updated successfully.") _LOGGER.info("ecobee data updated successfully.")
def setup(hass, config): def setup(hass, config):
""" """Setup Ecobee.
Setup Ecobee.
Will automatically load thermostat and sensor components to support Will automatically load thermostat and sensor components to support
devices discovered on the network. devices discovered on the network.
""" """

View File

@ -1,9 +1,4 @@
""" """Handle the frontend for Home Assistant."""
homeassistant.components.frontend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides a frontend for Home Assistant.
"""
import re import re
import os import os
import logging import logging
@ -32,7 +27,7 @@ _FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
def setup(hass, config): def setup(hass, config):
""" Setup serving the frontend. """ """Setup serving the frontend."""
for url in FRONTEND_URLS: for url in FRONTEND_URLS:
hass.http.register_path('GET', url, _handle_get_root, False) hass.http.register_path('GET', url, _handle_get_root, False)
@ -58,7 +53,7 @@ def setup(hass, config):
def _handle_get_api_bootstrap(handler, path_match, data): def _handle_get_api_bootstrap(handler, path_match, data):
""" Returns all data needed to bootstrap Home Assistant. """ """Return all data needed to bootstrap Home Assistant."""
hass = handler.server.hass hass = handler.server.hass
handler.write_json({ handler.write_json({
@ -70,7 +65,7 @@ def _handle_get_api_bootstrap(handler, path_match, data):
def _handle_get_root(handler, path_match, data): def _handle_get_root(handler, path_match, data):
""" Renders the frontend. """ """Render the frontend."""
handler.send_response(HTTP_OK) handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/html; charset=utf-8') handler.send_header('Content-type', 'text/html; charset=utf-8')
handler.end_headers() handler.end_headers()
@ -95,7 +90,7 @@ def _handle_get_root(handler, path_match, data):
def _handle_get_service_worker(handler, path_match, data): def _handle_get_service_worker(handler, path_match, data):
""" Returns service worker for the frontend. """ """Return service worker for the frontend."""
if handler.server.development: if handler.server.development:
sw_path = "home-assistant-polymer/build/service_worker.js" sw_path = "home-assistant-polymer/build/service_worker.js"
else: else:
@ -106,7 +101,7 @@ def _handle_get_service_worker(handler, path_match, data):
def _handle_get_static(handler, path_match, data): def _handle_get_static(handler, path_match, data):
""" Returns a static file for the frontend. """ """Return a static file for the frontend."""
req_file = util.sanitize_path(path_match.group('file')) req_file = util.sanitize_path(path_match.group('file'))
# Strip md5 hash out # Strip md5 hash out
@ -120,9 +115,7 @@ def _handle_get_static(handler, path_match, data):
def _handle_get_local(handler, path_match, data): def _handle_get_local(handler, path_match, data):
""" """Return a static file from the hass.config.path/www for the frontend."""
Returns a static file from the hass.config.path/www for the frontend.
"""
req_file = util.sanitize_path(path_match.group('file')) req_file = util.sanitize_path(path_match.group('file'))
path = handler.server.hass.config.path('www', req_file) path = handler.server.hass.config.path('www', req_file)

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by update_mdi script """ """DO NOT MODIFY. Auto-generated by update_mdi script."""
VERSION = "2f4adc5d3ad6d2f73bf69ed29b7594fd" VERSION = "e85dc66e1a0730e44f79ed11501cd79a"

View File

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

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 81ae753eb06a32bcac62cbee0981b1d24580e878 Subproject commit 0fed700045d6faba8eda8ec713ee9e6bc763507c

File diff suppressed because one or more lines are too long

View File

@ -33,13 +33,13 @@ _LOGGER = logging.getLogger(__name__)
def is_closed(hass, entity_id=None): def is_closed(hass, entity_id=None):
"""Returns if the garage door is closed based on the statemachine.""" """Return if the garage door is closed based on the statemachine."""
entity_id = entity_id or ENTITY_ID_ALL_GARAGE_DOORS entity_id = entity_id or ENTITY_ID_ALL_GARAGE_DOORS
return hass.states.is_state(entity_id, STATE_CLOSED) return hass.states.is_state(entity_id, STATE_CLOSED)
def close_door(hass, entity_id=None): def close_door(hass, entity_id=None):
"""Closes all or specified garage door.""" """Close all or a specified garage door."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE, data) hass.services.call(DOMAIN, SERVICE_CLOSE, data)
@ -58,7 +58,7 @@ def setup(hass, config):
component.setup(config) component.setup(config)
def handle_garage_door_service(service): def handle_garage_door_service(service):
"""Handles calls to the garage door services.""" """Handle calls to the garage door services."""
target_locks = component.extract_from_service(service) target_locks = component.extract_from_service(service)
for item in target_locks: for item in target_locks:
@ -81,7 +81,8 @@ def setup(hass, config):
class GarageDoorDevice(Entity): class GarageDoorDevice(Entity):
"""Represents a garage door.""" """Representation of a garage door."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
@property @property
def is_closed(self): def is_closed(self):
@ -98,7 +99,7 @@ class GarageDoorDevice(Entity):
@property @property
def state(self): def state(self):
"""Returns the state of the garage door.""" """Return the state of the garage door."""
closed = self.is_closed closed = self.is_closed
if closed is None: if closed is None:
return STATE_UNKNOWN return STATE_UNKNOWN

View File

@ -19,7 +19,9 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class DemoGarageDoor(GarageDoorDevice): class DemoGarageDoor(GarageDoorDevice):
"""Provides a demo garage door.""" """Provides a demo garage door."""
def __init__(self, name, state): def __init__(self, name, state):
"""Initialize the garage door."""
self._name = name self._name = name
self._state = state self._state = state

View File

@ -13,7 +13,7 @@ REQUIREMENTS = ['python-wink==0.6.2']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Sets up the Wink garage door platform.""" """Setup the Wink garage door platform."""
import pywink import pywink
if discovery_info is None: if discovery_info is None:
@ -32,19 +32,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkGarageDoorDevice(GarageDoorDevice): class WinkGarageDoorDevice(GarageDoorDevice):
"""Represents a Wink garage door.""" """Representation of a Wink garage door."""
def __init__(self, wink): def __init__(self, wink):
"""Initialize the garage door."""
self.wink = wink self.wink = wink
@property @property
def unique_id(self): def unique_id(self):
"""Returns the id of this wink garage door.""" """Return the ID of this wink garage door."""
return "{}.{}".format(self.__class__, self.wink.device_id()) return "{}.{}".format(self.__class__, self.wink.device_id())
@property @property
def name(self): def name(self):
"""Returns the name of the garage door if any.""" """Return the name of the garage door if any."""
return self.wink.name() return self.wink.name()
def update(self): def update(self):
@ -53,11 +54,11 @@ class WinkGarageDoorDevice(GarageDoorDevice):
@property @property
def is_closed(self): def is_closed(self):
"""Returns true if door is closed.""" """Return true if door is closed."""
return self.wink.state() == 0 return self.wink.state() == 0
def close_door(self): def close_door(self):
"""Closes the door.""" """Close the door."""
self.wink.set_state(0) self.wink.set_state(0)
def open_door(self): def open_door(self):

View File

@ -1,6 +1,5 @@
""" """
Component that records all events and state changes and feeds the data to Component that sends data to aGraphite installation.
a Graphite installation.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/graphite/ https://home-assistant.io/components/graphite/
@ -35,8 +34,10 @@ def setup(hass, config):
class GraphiteFeeder(threading.Thread): class GraphiteFeeder(threading.Thread):
"""Feeds data to Graphite.""" """Feed data to Graphite."""
def __init__(self, hass, host, port, prefix): def __init__(self, hass, host, port, prefix):
"""Initialize the feeder."""
super(GraphiteFeeder, self).__init__(daemon=True) super(GraphiteFeeder, self).__init__(daemon=True)
self._hass = hass self._hass = hass
self._host = host self._host = host

View File

@ -1,11 +1,11 @@
""" """
homeassistant.components.group Provides functionality to group entities.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to group devices that can be turned on or off.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/group/ https://home-assistant.io/components/group/
""" """
import threading
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME, ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME,
@ -32,7 +32,7 @@ _GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME),
def _get_group_on_off(state): def _get_group_on_off(state):
""" Determine the group on/off states based on a state. """ """Determine the group on/off states based on a state."""
for states in _GROUP_TYPES: for states in _GROUP_TYPES:
if state in states: if state in states:
return states return states
@ -41,7 +41,7 @@ def _get_group_on_off(state):
def is_on(hass, entity_id): def is_on(hass, entity_id):
""" Returns if the group state is in its ON-state. """ """Test if the group state is in its ON-state."""
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
if state: if state:
@ -54,8 +54,7 @@ def is_on(hass, entity_id):
def expand_entity_ids(hass, entity_ids): def expand_entity_ids(hass, entity_ids):
""" Returns the given list of entity ids and expands group ids into """Return entity_ids with group entity ids replaced by their members."""
the entity ids it represents if found. """
found_ids = [] found_ids = []
for entity_id in entity_ids: for entity_id in entity_ids:
@ -86,7 +85,7 @@ def expand_entity_ids(hass, entity_ids):
def get_entity_ids(hass, entity_id, domain_filter=None): def get_entity_ids(hass, entity_id, domain_filter=None):
""" Get the entity ids that make up this group. """ """Get members of this group."""
entity_id = entity_id.lower() entity_id = entity_id.lower()
try: try:
@ -107,7 +106,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
def setup(hass, config): def setup(hass, config):
""" Sets up all groups found definded in the configuration. """ """Setup all groups found definded in the configuration."""
for object_id, conf in config.get(DOMAIN, {}).items(): for object_id, conf in config.get(DOMAIN, {}).items():
if not isinstance(conf, dict): if not isinstance(conf, dict):
conf = {CONF_ENTITIES: conf} conf = {CONF_ENTITIES: conf}
@ -127,12 +126,12 @@ def setup(hass, config):
class Group(Entity): class Group(Entity):
""" Tracks a group of entity ids. """ """Track a group of entity ids."""
# pylint: disable=too-many-instance-attributes, too-many-arguments # pylint: disable=too-many-instance-attributes, too-many-arguments
def __init__(self, hass, name, entity_ids=None, user_defined=True, def __init__(self, hass, name, entity_ids=None, user_defined=True,
icon=None, view=False, object_id=None): icon=None, view=False, object_id=None):
"""Initialize a group."""
self.hass = hass self.hass = hass
self._name = name self._name = name
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
@ -146,6 +145,7 @@ class Group(Entity):
self.group_on = None self.group_on = None
self.group_off = None self.group_off = None
self._assumed_state = False self._assumed_state = False
self._lock = threading.Lock()
if entity_ids is not None: if entity_ids is not None:
self.update_tracked_entity_ids(entity_ids) self.update_tracked_entity_ids(entity_ids)
@ -154,26 +154,32 @@ class Group(Entity):
@property @property
def should_poll(self): def should_poll(self):
"""No need to poll because groups will update themselves."""
return False return False
@property @property
def name(self): def name(self):
"""Return the name of the group."""
return self._name return self._name
@property @property
def state(self): def state(self):
"""Return the state of the group."""
return self._state return self._state
@property @property
def icon(self): def icon(self):
"""Return the icon of the group."""
return self._icon return self._icon
@property @property
def hidden(self): def hidden(self):
"""If group should be hidden or not."""
return not self._user_defined or self._view return not self._user_defined or self._view
@property @property
def state_attributes(self): def state_attributes(self):
"""Return the state attributes for the group."""
data = { data = {
ATTR_ENTITY_ID: self.tracking, ATTR_ENTITY_ID: self.tracking,
ATTR_ORDER: self._order, ATTR_ORDER: self._order,
@ -186,11 +192,11 @@ class Group(Entity):
@property @property
def assumed_state(self): def assumed_state(self):
"""Return True if unable to access real state of entity.""" """Test if any member has an assumed state."""
return self._assumed_state return self._assumed_state
def update_tracked_entity_ids(self, entity_ids): def update_tracked_entity_ids(self, entity_ids):
""" Update the tracked entity IDs. """ """Update the member entity IDs."""
self.stop() self.stop()
self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) self.tracking = tuple(ent_id.lower() for ent_id in entity_ids)
self.group_on, self.group_off = None, None self.group_on, self.group_off = None, None
@ -200,30 +206,30 @@ class Group(Entity):
self.start() self.start()
def start(self): def start(self):
""" Starts the tracking. """ """Start tracking members."""
track_state_change( track_state_change(
self.hass, self.tracking, self._state_changed_listener) self.hass, self.tracking, self._state_changed_listener)
def stop(self): def stop(self):
""" Unregisters the group from Home Assistant. """ """Unregister the group from Home Assistant."""
self.hass.states.remove(self.entity_id) self.hass.states.remove(self.entity_id)
self.hass.bus.remove_listener( self.hass.bus.remove_listener(
ha.EVENT_STATE_CHANGED, self._state_changed_listener) ha.EVENT_STATE_CHANGED, self._state_changed_listener)
def update(self): def update(self):
""" Query all the tracked states and determine current group state. """ """Query all members and determine current group state."""
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
self._update_group_state() self._update_group_state()
def _state_changed_listener(self, entity_id, old_state, new_state): def _state_changed_listener(self, entity_id, old_state, new_state):
""" Listener to receive state changes of tracked entities. """ """Respond to a member state changing."""
self._update_group_state(new_state) self._update_group_state(new_state)
self.update_ha_state() self.update_ha_state()
@property @property
def _tracking_states(self): def _tracking_states(self):
"""States that the group is tracking.""" """The states that the group is tracking."""
states = [] states = []
for entity_id in self.tracking: for entity_id in self.tracking:
@ -242,8 +248,11 @@ class Group(Entity):
""" """
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# To store current states of group entities. Might not be needed. # To store current states of group entities. Might not be needed.
with self._lock:
states = None states = None
gr_state, gr_on, gr_off = self._state, self.group_on, self.group_off gr_state = self._state
gr_on = self.group_on
gr_off = self.group_off
# We have not determined type of group yet # We have not determined type of group yet
if gr_on is None: if gr_on is None:
@ -283,8 +292,9 @@ class Group(Entity):
if states is None: if states is None:
states = self._tracking_states states = self._tracking_states
self._assumed_state = any(state.attributes.get(ATTR_ASSUMED_STATE) self._assumed_state = any(
for state in states) state.attributes.get(ATTR_ASSUMED_STATE) for state
in states)
elif tr_state.attributes.get(ATTR_ASSUMED_STATE): elif tr_state.attributes.get(ATTR_ASSUMED_STATE):
self._assumed_state = True self._assumed_state = True

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.history
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provide pre-made queries on top of the recorder component. Provide pre-made queries on top of the recorder component.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
@ -11,7 +9,7 @@ from collections import defaultdict
from datetime import timedelta from datetime import timedelta
from itertools import groupby from itertools import groupby
import homeassistant.components.recorder as recorder from homeassistant.components import recorder, script
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.const import HTTP_BAD_REQUEST from homeassistant.const import HTTP_BAD_REQUEST
@ -19,13 +17,14 @@ DOMAIN = 'history'
DEPENDENCIES = ['recorder', 'http'] DEPENDENCIES = ['recorder', 'http']
SIGNIFICANT_DOMAINS = ('thermostat',) SIGNIFICANT_DOMAINS = ('thermostat',)
IGNORE_DOMAINS = ('zone', 'scene',)
URL_HISTORY_PERIOD = re.compile( URL_HISTORY_PERIOD = re.compile(
r'/api/history/period(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)') r'/api/history/period(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
def last_5_states(entity_id): def last_5_states(entity_id):
""" Return the last 5 states for entity_id. """ """Return the last 5 states for entity_id."""
entity_id = entity_id.lower() entity_id = entity_id.lower()
query = """ query = """
@ -38,17 +37,18 @@ def last_5_states(entity_id):
def get_significant_states(start_time, end_time=None, entity_id=None): def get_significant_states(start_time, end_time=None, entity_id=None):
"""Return states changes during UTC period start_time - end_time. """
Return states changes during UTC period start_time - end_time.
Significant states are all states where there is a state change, Significant states are all states where there is a state change,
as well as all states from certain domains (for instance as well as all states from certain domains (for instance
thermostat so that we get current temperature in our graphs). thermostat so that we get current temperature in our graphs).
""" """
where = """ where = """
(domain in ({}) or last_changed=last_updated) (domain IN ({}) OR last_changed=last_updated)
AND last_updated > ? AND domain NOT IN ({}) AND last_updated > ?
""".format(",".join(["'%s'" % x for x in SIGNIFICANT_DOMAINS])) """.format(",".join("'%s'" % x for x in SIGNIFICANT_DOMAINS),
",".join("'%s'" % x for x in IGNORE_DOMAINS))
data = [start_time] data = [start_time]
@ -63,15 +63,14 @@ def get_significant_states(start_time, end_time=None, entity_id=None):
query = ("SELECT * FROM states WHERE {} " query = ("SELECT * FROM states WHERE {} "
"ORDER BY entity_id, last_updated ASC").format(where) "ORDER BY entity_id, last_updated ASC").format(where)
states = recorder.query_states(query, data) states = (state for state in recorder.query_states(query, data)
if _is_significant(state))
return states_to_json(states, start_time, entity_id) return states_to_json(states, start_time, entity_id)
def state_changes_during_period(start_time, end_time=None, entity_id=None): def state_changes_during_period(start_time, end_time=None, entity_id=None):
""" """Return states changes during UTC period start_time - end_time."""
Return states changes during UTC period start_time - end_time.
"""
where = "last_changed=last_updated AND last_changed > ? " where = "last_changed=last_updated AND last_changed > ? "
data = [start_time] data = [start_time]
@ -92,7 +91,7 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
def get_states(utc_point_in_time, entity_ids=None, run=None): def get_states(utc_point_in_time, entity_ids=None, run=None):
""" Returns the states at a specific point in time. """ """Return the states at a specific point in time."""
if run is None: if run is None:
run = recorder.run_information(utc_point_in_time) run = recorder.run_information(utc_point_in_time)
@ -121,7 +120,7 @@ def get_states(utc_point_in_time, entity_ids=None, run=None):
def states_to_json(states, start_time, entity_id): def states_to_json(states, start_time, entity_id):
"""Converts SQL results into JSON friendly data structure. """Convert SQL results into JSON friendly data structure.
This takes our state list and turns it into a JSON friendly data This takes our state list and turns it into a JSON friendly data
structure {'entity_id': [list of states], 'entity_id2': [list of states]} structure {'entity_id': [list of states], 'entity_id2': [list of states]}
@ -130,7 +129,6 @@ def states_to_json(states, start_time, entity_id):
each list of states, otherwise our graphs won't start on the Y each list of states, otherwise our graphs won't start on the Y
axis correctly. axis correctly.
""" """
result = defaultdict(list) result = defaultdict(list)
entity_ids = [entity_id] if entity_id is not None else None entity_ids = [entity_id] if entity_id is not None else None
@ -148,7 +146,7 @@ def states_to_json(states, start_time, entity_id):
def get_state(utc_point_in_time, entity_id, run=None): def get_state(utc_point_in_time, entity_id, run=None):
""" Return a state at a specific point in time. """ """Return a state at a specific point in time."""
states = get_states(utc_point_in_time, (entity_id,), run) states = get_states(utc_point_in_time, (entity_id,), run)
return states[0] if states else None return states[0] if states else None
@ -156,7 +154,7 @@ def get_state(utc_point_in_time, entity_id, run=None):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup(hass, config): def setup(hass, config):
""" Setup history hooks. """ """Setup the history hooks."""
hass.http.register_path( hass.http.register_path(
'GET', 'GET',
re.compile( re.compile(
@ -172,14 +170,14 @@ def setup(hass, config):
# pylint: disable=unused-argument # pylint: disable=unused-argument
# pylint: disable=invalid-name # pylint: disable=invalid-name
def _api_last_5_states(handler, path_match, data): def _api_last_5_states(handler, path_match, data):
""" Return the last 5 states for an entity id as JSON. """ """Return the last 5 states for an entity id as JSON."""
entity_id = path_match.group('entity_id') entity_id = path_match.group('entity_id')
handler.write_json(last_5_states(entity_id)) handler.write_json(last_5_states(entity_id))
def _api_history_period(handler, path_match, data): def _api_history_period(handler, path_match, data):
""" Return history over a period of time. """ """Return history over a period of time."""
date_str = path_match.group('date') date_str = path_match.group('date')
one_day = timedelta(seconds=86400) one_day = timedelta(seconds=86400)
@ -200,3 +198,13 @@ def _api_history_period(handler, path_match, data):
handler.write_json( handler.write_json(
get_significant_states(start_time, end_time, entity_id).values()) get_significant_states(start_time, end_time, entity_id).values())
def _is_significant(state):
"""Test if state is significant for history charts.
Will only test for things that are not filtered out in SQL.
"""
# scripts that are not cancellable will never change state
return (state.domain != 'script' or
state.attributes.get(script.ATTR_CAN_CANCEL))

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.http
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module provides an API and a HTTP interface for debug purposes. This module provides an API and a HTTP interface for debug purposes.
For more details about the RESTful API, please refer to the documentation at For more details about the RESTful API, please refer to the documentation at
@ -52,7 +50,7 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
""" Sets up the HTTP API and debug interface. """ """Set up the HTTP API and debug interface."""
conf = config.get(DOMAIN, {}) conf = config.get(DOMAIN, {})
api_password = util.convert(conf.get(CONF_API_PASSWORD), str) api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
@ -87,15 +85,16 @@ def setup(hass, config):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
""" Handle HTTP requests in a threaded fashion. """ """Handle HTTP requests in a threaded fashion."""
# pylint: disable=too-few-public-methods
# pylint: disable=too-few-public-methods
allow_reuse_address = True allow_reuse_address = True
daemon_threads = True daemon_threads = True
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, server_address, request_handler_class, def __init__(self, server_address, request_handler_class,
hass, api_password, development, ssl_certificate, ssl_key): hass, api_password, development, ssl_certificate, ssl_key):
"""Initialize the server."""
super().__init__(server_address, request_handler_class) super().__init__(server_address, request_handler_class)
self.server_address = server_address self.server_address = server_address
@ -119,9 +118,9 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.socket = context.wrap_socket(self.socket, server_side=True) self.socket = context.wrap_socket(self.socket, server_side=True)
def start(self): def start(self):
""" Starts the HTTP server. """ """Start the HTTP server."""
def stop_http(event): def stop_http(event):
""" Stops the HTTP server. """ """Stop the HTTP server."""
self.shutdown() self.shutdown()
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http) self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
@ -140,19 +139,18 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.serve_forever() self.serve_forever()
def register_path(self, method, url, callback, require_auth=True): def register_path(self, method, url, callback, require_auth=True):
""" Registers a path with the server. """ """Register a path with the server."""
self.paths.append((method, url, callback, require_auth)) self.paths.append((method, url, callback, require_auth))
def log_message(self, fmt, *args): def log_message(self, fmt, *args):
""" Redirect built-in log to HA logging """ """Redirect built-in log to HA logging."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
_LOGGER.info(fmt, *args) _LOGGER.info(fmt, *args)
# pylint: disable=too-many-public-methods,too-many-locals # pylint: disable=too-many-public-methods,too-many-locals
class RequestHandler(SimpleHTTPRequestHandler): class RequestHandler(SimpleHTTPRequestHandler):
""" """Handle incoming HTTP requests.
Handles incoming HTTP requests
We extend from SimpleHTTPRequestHandler instead of Base so we We extend from SimpleHTTPRequestHandler instead of Base so we
can use the guess content type methods. can use the guess content type methods.
@ -161,13 +159,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
server_version = "HomeAssistant/1.0" server_version = "HomeAssistant/1.0"
def __init__(self, req, client_addr, server): def __init__(self, req, client_addr, server):
""" Contructor, call the base constructor and set up session """ """Constructor, call the base constructor and set up session."""
# Track if this was an authenticated request # Track if this was an authenticated request
self.authenticated = False self.authenticated = False
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server) SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def log_message(self, fmt, *arguments): def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """ """Redirect built-in log to HA logging."""
if self.server.api_password is None: if self.server.api_password is None:
_LOGGER.info(fmt, *arguments) _LOGGER.info(fmt, *arguments)
else: else:
@ -176,7 +174,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
if isinstance(arg, str) else arg for arg in arguments)) if isinstance(arg, str) else arg for arg in arguments))
def _handle_request(self, method): # pylint: disable=too-many-branches def _handle_request(self, method): # pylint: disable=too-many-branches
""" Does some common checks and calls appropriate method. """ """Perform some common checks and call appropriate method."""
url = urlparse(self.path) url = urlparse(self.path)
# Read query input. parse_qs gives a list for each value, we want last # Read query input. parse_qs gives a list for each value, we want last
@ -254,31 +252,31 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.end_headers() self.end_headers()
def do_HEAD(self): # pylint: disable=invalid-name def do_HEAD(self): # pylint: disable=invalid-name
""" HEAD request handler. """ """HEAD request handler."""
self._handle_request('HEAD') self._handle_request('HEAD')
def do_GET(self): # pylint: disable=invalid-name def do_GET(self): # pylint: disable=invalid-name
""" GET request handler. """ """GET request handler."""
self._handle_request('GET') self._handle_request('GET')
def do_POST(self): # pylint: disable=invalid-name def do_POST(self): # pylint: disable=invalid-name
""" POST request handler. """ """POST request handler."""
self._handle_request('POST') self._handle_request('POST')
def do_PUT(self): # pylint: disable=invalid-name def do_PUT(self): # pylint: disable=invalid-name
""" PUT request handler. """ """PUT request handler."""
self._handle_request('PUT') self._handle_request('PUT')
def do_DELETE(self): # pylint: disable=invalid-name def do_DELETE(self): # pylint: disable=invalid-name
""" DELETE request handler. """ """DELETE request handler."""
self._handle_request('DELETE') self._handle_request('DELETE')
def write_json_message(self, message, status_code=HTTP_OK): def write_json_message(self, message, status_code=HTTP_OK):
""" Helper method to return a message to the caller. """ """Helper method to return a message to the caller."""
self.write_json({'message': message}, status_code=status_code) self.write_json({'message': message}, status_code=status_code)
def write_json(self, data=None, status_code=HTTP_OK, location=None): def write_json(self, data=None, status_code=HTTP_OK, location=None):
""" Helper method to return JSON to the caller. """ """Helper method to return JSON to the caller."""
self.send_response(status_code) self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON)
@ -295,7 +293,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
cls=rem.JSONEncoder).encode("UTF-8")) cls=rem.JSONEncoder).encode("UTF-8"))
def write_text(self, message, status_code=HTTP_OK): def write_text(self, message, status_code=HTTP_OK):
""" Helper method to return a text message to the caller. """ """Helper method to return a text message to the caller."""
self.send_response(status_code) self.send_response(status_code)
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN) self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
@ -306,7 +304,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.wfile.write(message.encode("UTF-8")) self.wfile.write(message.encode("UTF-8"))
def write_file(self, path, cache_headers=True): def write_file(self, path, cache_headers=True):
""" Returns a file to the user. """ """Return a file to the user."""
try: try:
with open(path, 'rb') as inp: with open(path, 'rb') as inp:
self.write_file_pointer(self.guess_type(path), inp, self.write_file_pointer(self.guess_type(path), inp,
@ -318,10 +316,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
_LOGGER.exception("Unable to serve %s", path) _LOGGER.exception("Unable to serve %s", path)
def write_file_pointer(self, content_type, inp, cache_headers=True): def write_file_pointer(self, content_type, inp, cache_headers=True):
""" """Helper function to write a file pointer to the user."""
Helper function to write a file pointer to the user.
Does not do error handling.
"""
do_gzip = 'gzip' in self.headers.get(HTTP_HEADER_ACCEPT_ENCODING, '') do_gzip = 'gzip' in self.headers.get(HTTP_HEADER_ACCEPT_ENCODING, '')
self.send_response(HTTP_OK) self.send_response(HTTP_OK)
@ -354,7 +349,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.copyfile(inp, self.wfile) self.copyfile(inp, self.wfile)
def set_cache_header(self): def set_cache_header(self):
""" Add cache headers if not in development """ """Add cache headers if not in development."""
if self.server.development: if self.server.development:
return return
@ -369,7 +364,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.date_time_string(time.time()+cache_time)) self.date_time_string(time.time()+cache_time))
def set_session_cookie_header(self): def set_session_cookie_header(self):
""" Add the header for the session cookie and return session id. """ """Add the header for the session cookie and return session ID."""
if not self.authenticated: if not self.authenticated:
return None return None
@ -387,13 +382,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
return session_id return session_id
def verify_session(self): def verify_session(self):
""" Verify that we are in a valid session. """ """Verify that we are in a valid session."""
return self.get_cookie_session_id() is not None return self.get_cookie_session_id() is not None
def get_cookie_session_id(self): def get_cookie_session_id(self):
""" """Extract the current session ID from the cookie.
Extracts the current session id from the
cookie or returns None if not set or invalid Return None if not set or invalid.
""" """
if 'Cookie' not in self.headers: if 'Cookie' not in self.headers:
return None return None
@ -417,7 +412,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
return None return None
def destroy_session(self): def destroy_session(self):
""" Destroys session. """ """Destroy the session."""
session_id = self.get_cookie_session_id() session_id = self.get_cookie_session_id()
if session_id is None: if session_id is None:
@ -428,27 +423,28 @@ class RequestHandler(SimpleHTTPRequestHandler):
def session_valid_time(): def session_valid_time():
""" Time till when a session will be valid. """ """Time till when a session will be valid."""
return date_util.utcnow() + timedelta(seconds=SESSION_TIMEOUT_SECONDS) return date_util.utcnow() + timedelta(seconds=SESSION_TIMEOUT_SECONDS)
class SessionStore(object): class SessionStore(object):
""" Responsible for storing and retrieving http sessions """ """Responsible for storing and retrieving HTTP sessions."""
def __init__(self): def __init__(self):
""" Set up the session store """ """Setup the session store."""
self._sessions = {} self._sessions = {}
self._lock = threading.RLock() self._lock = threading.RLock()
@util.Throttle(SESSION_CLEAR_INTERVAL) @util.Throttle(SESSION_CLEAR_INTERVAL)
def _remove_expired(self): def _remove_expired(self):
""" Remove any expired sessions. """ """Remove any expired sessions."""
now = date_util.utcnow() now = date_util.utcnow()
for key in [key for key, valid_time in self._sessions.items() for key in [key for key, valid_time in self._sessions.items()
if valid_time < now]: if valid_time < now]:
self._sessions.pop(key) self._sessions.pop(key)
def is_valid(self, key): def is_valid(self, key):
""" Return True if a valid session is given. """ """Return True if a valid session is given."""
with self._lock: with self._lock:
self._remove_expired() self._remove_expired()
@ -456,19 +452,19 @@ class SessionStore(object):
self._sessions[key] > date_util.utcnow()) self._sessions[key] > date_util.utcnow())
def extend_validation(self, key): def extend_validation(self, key):
""" Extend a session validation time. """ """Extend a session validation time."""
with self._lock: with self._lock:
if key not in self._sessions: if key not in self._sessions:
return return
self._sessions[key] = session_valid_time() self._sessions[key] = session_valid_time()
def destroy(self, key): def destroy(self, key):
""" Destroy a session by key. """ """Destroy a session by key."""
with self._lock: with self._lock:
self._sessions.pop(key, None) self._sessions.pop(key, None)
def create(self): def create(self):
""" Creates a new session. """ """Create a new session."""
with self._lock: with self._lock:
session_id = util.get_random_string(20) session_id = util.get_random_string(20)

View File

@ -1,7 +1,5 @@
""" """
homeassistant.components.ifttt Support to trigger Maker IFTTT recipes.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component enable you to trigger Maker IFTTT recipes.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ifttt/ https://home-assistant.io/components/ifttt/
@ -27,7 +25,7 @@ REQUIREMENTS = ['pyfttt==0.3']
def trigger(hass, event, value1=None, value2=None, value3=None): def trigger(hass, event, value1=None, value2=None, value3=None):
""" Trigger a Maker IFTTT recipe. """ """Trigger a Maker IFTTT recipe."""
data = { data = {
ATTR_EVENT: event, ATTR_EVENT: event,
ATTR_VALUE1: value1, ATTR_VALUE1: value1,
@ -38,15 +36,14 @@ def trigger(hass, event, value1=None, value2=None, value3=None):
def setup(hass, config): def setup(hass, config):
""" Setup the ifttt service component. """ """Setup the IFTTT service component."""
if not validate_config(config, {DOMAIN: ['key']}, _LOGGER): if not validate_config(config, {DOMAIN: ['key']}, _LOGGER):
return False return False
key = config[DOMAIN]['key'] key = config[DOMAIN]['key']
def trigger_service(call): def trigger_service(call):
""" Handle ifttt trigger service calls. """ """Handle IFTTT trigger service calls."""
event = call.data.get(ATTR_EVENT) event = call.data.get(ATTR_EVENT)
value1 = call.data.get(ATTR_VALUE1) value1 = call.data.get(ATTR_VALUE1)
value2 = call.data.get(ATTR_VALUE2) value2 = call.data.get(ATTR_VALUE2)

View File

@ -31,8 +31,10 @@ CONF_USERNAME = 'username'
CONF_PASSWORD = 'password' CONF_PASSWORD = 'password'
CONF_SSL = 'ssl' CONF_SSL = 'ssl'
CONF_VERIFY_SSL = 'verify_ssl' CONF_VERIFY_SSL = 'verify_ssl'
CONF_BLACKLIST = 'blacklist'
# pylint: disable=too-many-locals
def setup(hass, config): def setup(hass, config):
"""Setup the InfluxDB component.""" """Setup the InfluxDB component."""
from influxdb import InfluxDBClient, exceptions from influxdb import InfluxDBClient, exceptions
@ -52,6 +54,7 @@ def setup(hass, config):
ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL) ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool, verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool,
DEFAULT_VERIFY_SSL) DEFAULT_VERIFY_SSL)
blacklist = conf.get(CONF_BLACKLIST, [])
try: try:
influx = InfluxDBClient(host=host, port=port, username=username, influx = InfluxDBClient(host=host, port=port, username=username,
@ -67,7 +70,8 @@ def setup(hass, config):
def influx_event_listener(event): def influx_event_listener(event):
"""Listen for new messages on the bus and sends them to Influx.""" """Listen for new messages on the bus and sends them to Influx."""
state = event.data.get('new_state') state = event.data.get('new_state')
if state is None or state.state in (STATE_UNKNOWN, ''): if state is None or state.state in (STATE_UNKNOWN, '') \
or state.entity_id in blacklist:
return return
try: try:

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.input_boolean
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to keep track of user controlled booleans for within automation. Component to keep track of user controlled booleans for within automation.
For more details about this component, please refer to the documentation For more details about this component, please refer to the documentation
@ -41,7 +39,7 @@ def turn_off(hass, entity_id):
def setup(hass, config): def setup(hass, config):
""" Set up input boolean. """ """Set up input boolean."""
if not isinstance(config.get(DOMAIN), dict): if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN) _LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False return False
@ -68,7 +66,7 @@ def setup(hass, config):
return False return False
def toggle_service(service): def toggle_service(service):
""" Handle a calls to the input boolean services. """ """Handle a calls to the input boolean services."""
target_inputs = component.extract_from_service(service) target_inputs = component.extract_from_service(service)
for input_b in target_inputs: for input_b in target_inputs:
@ -86,10 +84,10 @@ def setup(hass, config):
class InputBoolean(ToggleEntity): class InputBoolean(ToggleEntity):
""" Represent a boolean input. """ """Representation of a boolean input."""
def __init__(self, object_id, name, state, icon): def __init__(self, object_id, name, state, icon):
""" Initialize a boolean input. """ """Initialize a boolean input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id) self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name self._name = name
self._state = state self._state = state
@ -97,22 +95,22 @@ class InputBoolean(ToggleEntity):
@property @property
def should_poll(self): def should_poll(self):
"""If entitiy should be polled.""" """If entity should be polled."""
return False return False
@property @property
def name(self): def name(self):
"""Name of the boolean input.""" """Return name of the boolean input."""
return self._name return self._name
@property @property
def icon(self): def icon(self):
"""Icon to be used for this entity.""" """Returh the icon to be used for this entity."""
return self._icon return self._icon
@property @property
def is_on(self): def is_on(self):
"""True if entity is on.""" """Return true if entity is on."""
return self._state return self._state
def turn_on(self, **kwargs): def turn_on(self, **kwargs):

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.input_select
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to offer a way to select an option from a list. Component to offer a way to select an option from a list.
For more details about this component, please refer to the documentation For more details about this component, please refer to the documentation
@ -29,7 +27,7 @@ SERVICE_SELECT_OPTION = 'select_option'
def select_option(hass, entity_id, option): def select_option(hass, entity_id, option):
""" Set input_select to False. """ """Set input_select to False."""
hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, { hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
ATTR_ENTITY_ID: entity_id, ATTR_ENTITY_ID: entity_id,
ATTR_OPTION: option, ATTR_OPTION: option,
@ -37,7 +35,7 @@ def select_option(hass, entity_id, option):
def setup(hass, config): def setup(hass, config):
""" Set up input select. """ """Setup input select."""
if not isinstance(config.get(DOMAIN), dict): if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN) _LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False return False
@ -77,7 +75,7 @@ def setup(hass, config):
return False return False
def select_option_service(call): def select_option_service(call):
""" Handle a calls to the input select services. """ """Handle a calls to the input select services."""
target_inputs = component.extract_from_service(call) target_inputs = component.extract_from_service(call)
for input_select in target_inputs: for input_select in target_inputs:
@ -92,11 +90,11 @@ def setup(hass, config):
class InputSelect(Entity): class InputSelect(Entity):
""" Represent a select input. """ """Representation of a select input."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, object_id, name, state, options, icon): def __init__(self, object_id, name, state, options, icon):
""" Initialize a select input. """ """Initialize a select input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id) self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name self._name = name
self._current_option = state self._current_option = state
@ -105,33 +103,33 @@ class InputSelect(Entity):
@property @property
def should_poll(self): def should_poll(self):
""" If entity should be polled. """ """If entity should be polled."""
return False return False
@property @property
def name(self): def name(self):
""" Name of the select input. """ """Return the name of the select input."""
return self._name return self._name
@property @property
def icon(self): def icon(self):
""" Icon to be used for this entity. """ """Return the icon to be used for this entity."""
return self._icon return self._icon
@property @property
def state(self): def state(self):
""" State of the component. """ """Return the state of the component."""
return self._current_option return self._current_option
@property @property
def state_attributes(self): def state_attributes(self):
""" State attributes. """ """Return the state attributes."""
return { return {
ATTR_OPTIONS: self._options, ATTR_OPTIONS: self._options,
} }
def select_option(self, option): def select_option(self, option):
""" Select new option. """ """Select new option."""
if option not in self._options: if option not in self._options:
_LOGGER.warning('Invalid option: %s (possible options: %s)', _LOGGER.warning('Invalid option: %s (possible options: %s)',
option, ', '.join(self._options)) option, ', '.join(self._options))

View File

@ -1,6 +1,4 @@
""" """
homeassistant.components.insteon_hub
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Insteon Hub. Support for Insteon Hub.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
@ -24,8 +22,8 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config): def setup(hass, config):
""" """Setup Insteon Hub component.
Setup Insteon Hub component.
This will automatically import associated lights. This will automatically import associated lights.
""" """
if not validate_config( if not validate_config(
@ -58,24 +56,25 @@ def setup(hass, config):
class InsteonToggleDevice(ToggleEntity): class InsteonToggleDevice(ToggleEntity):
""" Abstract Class for an Insteon node. """ """An abstract Class for an Insteon node."""
def __init__(self, node): def __init__(self, node):
"""Initialize the device."""
self.node = node self.node = node
self._value = 0 self._value = 0
@property @property
def name(self): def name(self):
""" Returns the name of the node. """ """Return the the name of the node."""
return self.node.DeviceName return self.node.DeviceName
@property @property
def unique_id(self): def unique_id(self):
""" Returns the id of this insteon node. """ """Return the ID of this insteon node."""
return self.node.DeviceID return self.node.DeviceID
def update(self): def update(self):
""" Update state of the sensor. """ """Update state of the sensor."""
resp = self.node.send_command('get_status', wait=True) resp = self.node.send_command('get_status', wait=True)
try: try:
self._value = resp['response']['level'] self._value = resp['response']['level']
@ -84,11 +83,13 @@ class InsteonToggleDevice(ToggleEntity):
@property @property
def is_on(self): def is_on(self):
""" Returns boolean response if the node is on. """ """Return the boolean response if the node is on."""
return self._value != 0 return self._value != 0
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn device on."""
self.node.send_command('on') self.node.send_command('on')
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn device off."""
self.node.send_command('off') self.node.send_command('off')

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