Compare commits
285 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 763a9ce8c6 | |||
| e53adf003c | |||
| 3df946aa9e | |||
| 2285a6761c | |||
| b85753d5da | |||
| ce4933d637 | |||
| de6fc771cb | |||
| 7f5109e8f3 | |||
| de68be06dd | |||
| 950cc9e618 | |||
| a3d505f45e | |||
| befdecc3b0 | |||
| b92a51c1c3 | |||
| 20f8935b86 | |||
| 652c666d14 | |||
| 9157bd38e8 | |||
| f0970f4104 | |||
| 41f205e09d | |||
| 72c40ab826 | |||
| 01b216f6cb | |||
| 5b18ea4237 | |||
| 70ce179224 | |||
| 0b9699fd4b | |||
| 8797ea8f37 | |||
| dd691a4684 | |||
| b6a4257753 | |||
| 40e17da415 | |||
| fef682b192 | |||
| 369d234bda | |||
| 5085e337e5 | |||
| 10cb8c0799 | |||
| 565ae8d30f | |||
| 2c770164f2 | |||
| 38e6f8fdab | |||
| a0be348f3a | |||
| f7943d9448 | |||
| 890930ea32 | |||
| 2d91dce6d0 | |||
| 154d184247 | |||
| 015527aa5f | |||
| 5bd004ee38 | |||
| 8fe0740b7f | |||
| c22e106d44 | |||
| 4792b9f40c | |||
| 0490514d9e | |||
| fcfa6f2691 | |||
| ca788b6e4d | |||
| fd7d6a9d53 | |||
| 6ee3ca8264 | |||
| a8761e1ef8 | |||
| 557dae7ab3 | |||
| 454688cc17 | |||
| 59dc8da365 | |||
| bb658412c4 | |||
| 505b3b198e | |||
| 0bf13dbf26 | |||
| 4e4b24fcff | |||
| 72fdd94b29 | |||
| c6f66de16e | |||
| 41021e63d0 | |||
| 0bbcc81285 | |||
| 302f32eacd | |||
| c093a2bf54 | |||
| 792954adc9 | |||
| bb5c80b8f5 | |||
| 3f3bfbbb94 | |||
| a1f8466754 | |||
| 8a38ba5954 | |||
| 208a7c9e60 | |||
| 27aba5c834 | |||
| a64726e321 | |||
| c2204433bd | |||
| fa15d86c2b | |||
| 4e7160139e | |||
| 47d5c4f437 | |||
| 179c6efff7 | |||
| 6e32d99174 | |||
| fd27e4fc7d | |||
| 9fc73fa644 | |||
| ba0cbab6cb | |||
| 83778a0f55 | |||
| b29eff5ef1 | |||
| c865ff852d | |||
| 1f7e0936fa | |||
| a855a9bfcd | |||
| 9fe17423ea | |||
| b7b34b280f | |||
| 971fe8bc7b | |||
| 95d5c456d7 | |||
| dd33831cb1 | |||
| 5abff70a87 | |||
| d28116f2bf | |||
| cde05b91ce | |||
| 663e4d57d9 | |||
| cf285e0a0a | |||
| dd607ea84a | |||
| b4458b7f52 | |||
| 8a6cc49438 | |||
| 2bdad928d0 | |||
| 3e3d1ae9de | |||
| fb3586c18f | |||
| fe2adff017 | |||
| e5c8dd03e1 | |||
| d6344d6492 | |||
| 399fda079f | |||
| 6bc3e0bb58 | |||
| 54b60c6564 | |||
| 61b387cd0b | |||
| b7044a79b2 | |||
| 368668784a | |||
| 7099030cd7 | |||
| eb9ed5ccfe | |||
| fc455a1047 | |||
| 956f6056f9 | |||
| f8d2da2ace | |||
| 7d23a5f7e4 | |||
| 8386bda4e4 | |||
| 13d7f742a7 | |||
| b67964274b | |||
| 6797365d4f | |||
| 2410942fd0 | |||
| 2a75c6fcf6 | |||
| 159e287959 | |||
| a01111ae56 | |||
| 4599fd6dae | |||
| ddc139b853 | |||
| 2b6f0586b4 | |||
| 9904667a80 | |||
| 097ded73b1 | |||
| 5b2093ebc1 | |||
| 1fa2f23864 | |||
| 2e2537de2a | |||
| 1f8b66511d | |||
| 312d0320c6 | |||
| 284a1fd08e | |||
| 1fbf7afc6a | |||
| 025713ee41 | |||
| 81e5a852f0 | |||
| 4637a83c29 | |||
| ce82bd7515 | |||
| 534308e461 | |||
| 84bdba28b3 | |||
| 8af7858415 | |||
| 56ef16e858 | |||
| b9856a2af5 | |||
| 14a9b9fe3f | |||
| 47c4f66886 | |||
| 0d9b8a0d06 | |||
| c56701baaf | |||
| f22a40c3e8 | |||
| 986c9c55bf | |||
| 9838697d2b | |||
| d1256d4889 | |||
| d6eab03a61 | |||
| b534244e40 | |||
| d784610c52 | |||
| 8bff97083c | |||
| 3e4412d375 | |||
| 567727fb3b | |||
| 49ebc6d0b0 | |||
| fd5e2d1321 | |||
| 652f059d6a | |||
| cf7c2c492a | |||
| 7f1e181c9b | |||
| 08233da8a7 | |||
| 5423252a52 | |||
| 036cfe4ef6 | |||
| db6212dae3 | |||
| 391c6a358a | |||
| 29a651dd92 | |||
| 20dd782d9e | |||
| 9d76c58bd4 | |||
| c21918233b | |||
| 87008f23a1 | |||
| ba0a25aef6 | |||
| ee728345f6 | |||
| 2db873322d | |||
| 8ecced5fe6 | |||
| 897b5c668f | |||
| 4f536ac63d | |||
| 876978d64a | |||
| 91731c7234 | |||
| 1b8b2acb51 | |||
| 095dd70391 | |||
| 6879e39d6c | |||
| 7e8e91ef3c | |||
| ac43d23bd7 | |||
| cc7a4d545e | |||
| 40be9f4e0c | |||
| cd5b5c55e2 | |||
| 3a4250133a | |||
| 7035af6634 | |||
| fb7bd1bfe1 | |||
| f6bc1a4575 | |||
| 5ba9ac6465 | |||
| cfd65e48cb | |||
| b8a40457ee | |||
| 7ff9aecd4e | |||
| 032f06e015 | |||
| 6ac9210919 | |||
| b47f1c3a96 | |||
| 5222c19b4c | |||
| 1e97d31711 | |||
| 18f48191d9 | |||
| abc7243bc8 | |||
| 4c64be5cfa | |||
| 199375bd25 | |||
| 69bbfe5372 | |||
| d2c94da938 | |||
| 4e3c8a8697 | |||
| d75d08e97c | |||
| cd30f2de0d | |||
| bdad69307a | |||
| b16c17fae5 | |||
| 91ee889527 | |||
| 7b45001879 | |||
| fb46eff5f8 | |||
| 9701be9e9c | |||
| 605a572c86 | |||
| 1159360281 | |||
| 41214fd082 | |||
| d30fb7f04a | |||
| e57eca517b | |||
| 931dc27300 | |||
| d7094b996a | |||
| 9654f6b4e4 | |||
| c04555d7b1 | |||
| e755731588 | |||
| a687a3decb | |||
| 88c834fdd3 | |||
| feb1a15489 | |||
| 4d5e9f2931 | |||
| 1e3199eb8c | |||
| 17493660df | |||
| e59d6b7da0 | |||
| 2fd0b28c53 | |||
| bbd3a43585 | |||
| a02840379d | |||
| d641cd5eef | |||
| 6c518c74ec | |||
| fcbeafc6db | |||
| 4370f5170b | |||
| 49c6484f72 | |||
| ec642cbeff | |||
| 2823d8f0d8 | |||
| bfa579f8b2 | |||
| be04ebf9ed | |||
| 87e23e7d73 | |||
| c586111c8c | |||
| e92c77a113 | |||
| 2d2b26ff1a | |||
| eed5b1e41b | |||
| 029094e549 | |||
| 28d80e6e2d | |||
| 5449d53e07 | |||
| 112b85877f | |||
| 0f6ec9b7ac | |||
| a82931022c | |||
| 1974e2cfbe | |||
| e0ba33300a | |||
| 566c143ee3 | |||
| 014b11c61d | |||
| b70a455a13 | |||
| 9da8679dbd | |||
| c14ca9983c | |||
| 9c4fe6e7fe | |||
| a8edcfd315 | |||
| 0193454064 | |||
| 94633b3795 | |||
| 49982ac83c | |||
| 72940da874 | |||
| 5acf995d71 | |||
| f623a332cf | |||
| f5dd41e019 | |||
| 97edc39d0a | |||
| 2b4be33a3d | |||
| ac69db8133 | |||
| 5a126736e4 | |||
| 8e9c557a2c | |||
| d12dfc33a5 | |||
| 1be90081ef | |||
| 019af42e94 | |||
| 88e7967a7d | |||
| e37c232bf6 | |||
| fb449cbc82 |
+12
-5
@@ -26,11 +26,13 @@ omit =
|
||||
homeassistant/components/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/tellstick.py
|
||||
homeassistant/components/*/tellstick.py
|
||||
|
||||
homeassistant/components/tellduslive.py
|
||||
homeassistant/components/*/tellduslive.py
|
||||
|
||||
homeassistant/components/vera.py
|
||||
homeassistant/components/*/vera.py
|
||||
|
||||
homeassistant/components/ecobee.py
|
||||
@@ -51,9 +53,6 @@ omit =
|
||||
homeassistant/components/zwave.py
|
||||
homeassistant/components/*/zwave.py
|
||||
|
||||
homeassistant/components/rfxtrx.py
|
||||
homeassistant/components/*/rfxtrx.py
|
||||
|
||||
homeassistant/components/mysensors.py
|
||||
homeassistant/components/*/mysensors.py
|
||||
|
||||
@@ -108,9 +107,12 @@ omit =
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
homeassistant/components/media_player/sonos.py
|
||||
homeassistant/components/media_player/squeezebox.py
|
||||
homeassistant/components/media_player/yamaha.py
|
||||
homeassistant/components/notify/free_mobile.py
|
||||
homeassistant/components/notify/googlevoice.py
|
||||
homeassistant/components/notify/gntp.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/message_bird.py
|
||||
homeassistant/components/notify/nma.py
|
||||
homeassistant/components/notify/pushbullet.py
|
||||
homeassistant/components/notify/pushetta.py
|
||||
@@ -123,6 +125,7 @@ omit =
|
||||
homeassistant/components/notify/telegram.py
|
||||
homeassistant/components/notify/twitter.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/scene/hunterdouglas_powerview.py
|
||||
homeassistant/components/sensor/arest.py
|
||||
homeassistant/components/sensor/bitcoin.py
|
||||
homeassistant/components/sensor/cpuspeed.py
|
||||
@@ -139,8 +142,8 @@ omit =
|
||||
homeassistant/components/sensor/openweathermap.py
|
||||
homeassistant/components/sensor/rest.py
|
||||
homeassistant/components/sensor/sabnzbd.py
|
||||
homeassistant/components/sensor/steam_online.py
|
||||
homeassistant/components/sensor/speedtest.py
|
||||
homeassistant/components/sensor/steam_online.py
|
||||
homeassistant/components/sensor/swiss_public_transport.py
|
||||
homeassistant/components/sensor/systemmonitor.py
|
||||
homeassistant/components/sensor/temper.py
|
||||
@@ -148,20 +151,24 @@ omit =
|
||||
homeassistant/components/sensor/torque.py
|
||||
homeassistant/components/sensor/transmission.py
|
||||
homeassistant/components/sensor/twitch.py
|
||||
homeassistant/components/sensor/uber.py
|
||||
homeassistant/components/sensor/worldclock.py
|
||||
homeassistant/components/switch/arest.py
|
||||
homeassistant/components/switch/edimax.py
|
||||
homeassistant/components/switch/dlink.py
|
||||
homeassistant/components/switch/edimax.py
|
||||
homeassistant/components/switch/hikvisioncam.py
|
||||
homeassistant/components/switch/mystrom.py
|
||||
homeassistant/components/switch/orvibo.py
|
||||
homeassistant/components/switch/pulseaudio_loopback.py
|
||||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/transmission.py
|
||||
homeassistant/components/switch/wake_on_lan.py
|
||||
homeassistant/components/thermostat/heatmiser.py
|
||||
homeassistant/components/thermostat/homematic.py
|
||||
homeassistant/components/thermostat/proliphix.py
|
||||
homeassistant/components/thermostat/radiotherm.py
|
||||
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
**Related issue (if applicable):** #
|
||||
|
||||
**Example entry for `configuration.yaml` (if applicable):**
|
||||
```yaml
|
||||
|
||||
@@ -9,17 +10,16 @@
|
||||
|
||||
**Checklist:**
|
||||
|
||||
- [ ] Local tests with `tox` ran successfully.
|
||||
- [ ] No CI failures. **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.
|
||||
- If code communicates with devices:
|
||||
- [ ] 3rd party library/libraries for communication is/are added as dependencies via the `REQUIREMENTS` variable ([example][ex-requir]).
|
||||
- [ ] 3rd party dependencies are 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.
|
||||
If code communicates with devices:
|
||||
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
|
||||
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
|
||||
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
|
||||
- [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
|
||||
- [ ] New files were added to `.coveragerc`.
|
||||
- If the code does not depend on external Python module:
|
||||
- [ ] Tests to verify that the code works are included.
|
||||
- [ ] [Commits will be squashed][squash] when the PR is ready to be merged.
|
||||
|
||||
If the code does not interact with devices:
|
||||
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
|
||||
- [ ] Tests have been added to verify that the new code works.
|
||||
|
||||
[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
|
||||
|
||||
+4
-4
@@ -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.
|
||||
|
||||
```shell
|
||||
> pip install -U tox
|
||||
> tox
|
||||
```
|
||||
```bash
|
||||
> pip install -U 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.
|
||||
|
||||
|
||||
+5
-7
@@ -6,19 +6,17 @@ VOLUME /config
|
||||
RUN mkdir -p /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
|
||||
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/*
|
||||
|
||||
COPY script/build_python_openzwave script/build_python_openzwave
|
||||
RUN apt-get update && \
|
||||
apt-get install -y cython3 libudev-dev && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||
pip3 install "cython<0.23" && \
|
||||
script/build_python_openzwave
|
||||
RUN script/build_python_openzwave && \
|
||||
mkdir -p /usr/local/share/python-openzwave && \
|
||||
ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config
|
||||
|
||||
COPY requirements_all.txt requirements_all.txt
|
||||
RUN pip3 install --no-cache-dir -r requirements_all.txt
|
||||
|
||||
@@ -101,9 +101,10 @@ def track_devices(hass, entity_id, old_state, new_state):
|
||||
|
||||
@track_time_change(hour=7, minute=0, second=0)
|
||||
def wake_up(hass, now):
|
||||
"""
|
||||
Turn it on in the morning (7 AM) if there are people home and
|
||||
it is not already on.
|
||||
"""Turn light on in the morning.
|
||||
|
||||
Turn the light on at 7 AM if there are people home and it is not already
|
||||
on.
|
||||
"""
|
||||
if not TARGET_ID:
|
||||
return
|
||||
@@ -126,8 +127,9 @@ def all_lights_off(hass, entity_id, old_state, new_state):
|
||||
|
||||
@service(DOMAIN, SERVICE_FLASH)
|
||||
def flash_service(hass, call):
|
||||
"""
|
||||
Service that will turn the target off for 10 seconds if on and vice versa.
|
||||
"""Service that will toggle the target.
|
||||
|
||||
Set the light to off for 10 seconds if on and vice versa.
|
||||
"""
|
||||
if not TARGET_ID:
|
||||
return
|
||||
|
||||
@@ -20,7 +20,6 @@ DEPENDENCIES = []
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup our skeleton component."""
|
||||
|
||||
# States are in the format DOMAIN.OBJECT_ID.
|
||||
hass.states.set('hello_world.Hello_World', 'Works!')
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Init file for Home Assistant."""
|
||||
|
||||
+33
-27
@@ -1,4 +1,4 @@
|
||||
""" Starts home assistant. """
|
||||
"""Starts home assistant."""
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
@@ -12,21 +12,26 @@ from multiprocessing import Process
|
||||
import homeassistant.config as config_util
|
||||
from homeassistant import bootstrap
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, RESTART_EXIT_CODE, __version__)
|
||||
__version__,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
REQUIRED_PYTHON_VER,
|
||||
RESTART_EXIT_CODE,
|
||||
)
|
||||
|
||||
|
||||
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]
|
||||
req_major, req_minor = REQUIRED_PYTHON_VER
|
||||
|
||||
if major < 3 or (major == 3 and minor < 4):
|
||||
print("Home Assistant requires atleast Python 3.4")
|
||||
if major < req_major or (major == req_major and minor < req_minor):
|
||||
print("Home Assistant requires at least Python {}.{}".format(
|
||||
req_major, req_minor))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def ensure_config_path(config_dir):
|
||||
""" Validates configuration directory. """
|
||||
|
||||
"""Validate the configuration directory."""
|
||||
lib_dir = os.path.join(config_dir, 'lib')
|
||||
|
||||
# Test if configuration directory exists
|
||||
@@ -54,7 +59,7 @@ def ensure_config_path(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)
|
||||
|
||||
if config_path is None:
|
||||
@@ -65,7 +70,7 @@ def ensure_config_file(config_dir):
|
||||
|
||||
|
||||
def get_arguments():
|
||||
""" Get parsed passed in arguments. """
|
||||
"""Get parsed passed in arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Home Assistant: Observe, Control, Automate.")
|
||||
parser.add_argument('--version', action='version', version=__version__)
|
||||
@@ -130,25 +135,25 @@ def get_arguments():
|
||||
|
||||
|
||||
def daemonize():
|
||||
""" Move current process to daemon process """
|
||||
# create first fork
|
||||
"""Move current process to daemon process."""
|
||||
# Create first fork
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0)
|
||||
|
||||
# decouple fork
|
||||
# Decouple fork
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
# create second fork
|
||||
# Create second fork
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def check_pid(pid_file):
|
||||
""" Check that HA is not already running """
|
||||
# check pid file
|
||||
"""Check that HA is not already running."""
|
||||
# Check pid file
|
||||
try:
|
||||
pid = int(open(pid_file, 'r').readline())
|
||||
except IOError:
|
||||
@@ -165,7 +170,7 @@ def check_pid(pid_file):
|
||||
|
||||
|
||||
def write_pid(pid_file):
|
||||
""" Create PID File """
|
||||
"""Create a PID File."""
|
||||
pid = os.getpid()
|
||||
try:
|
||||
open(pid_file, 'w').write(str(pid))
|
||||
@@ -175,7 +180,7 @@ def write_pid(pid_file):
|
||||
|
||||
|
||||
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:
|
||||
hass_path = inp.read().strip()
|
||||
|
||||
@@ -207,7 +212,7 @@ def install_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")
|
||||
os.popen('launchctl unload ' + path)
|
||||
|
||||
@@ -215,9 +220,10 @@ def uninstall_osx():
|
||||
|
||||
|
||||
def setup_and_run_hass(config_dir, args, top_process=False):
|
||||
"""
|
||||
Setup HASS and run. Block until stopped. Will assume it is running in a
|
||||
subprocess unless top_process is set to true.
|
||||
"""Setup HASS and run.
|
||||
|
||||
Block until stopped. Will assume it is running in a subprocess unless
|
||||
top_process is set to true.
|
||||
"""
|
||||
if args.demo_mode:
|
||||
config = {
|
||||
@@ -237,7 +243,7 @@ def setup_and_run_hass(config_dir, args, top_process=False):
|
||||
|
||||
if args.open_ui:
|
||||
def open_browser(event):
|
||||
""" Open the webinterface in a browser. """
|
||||
"""Open the webinterface in a browser."""
|
||||
if hass.config.api is not None:
|
||||
import webbrowser
|
||||
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):
|
||||
""" 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()
|
||||
hass_proc.daemon = True
|
||||
|
||||
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()
|
||||
hass_proc.terminate()
|
||||
|
||||
@@ -283,7 +289,7 @@ def run_hass_process(hass_proc):
|
||||
|
||||
|
||||
def main():
|
||||
""" Starts Home Assistant. """
|
||||
"""Start Home Assistant."""
|
||||
validate_python()
|
||||
|
||||
args = get_arguments()
|
||||
@@ -291,7 +297,7 @@ def main():
|
||||
config_dir = os.path.join(os.getcwd(), args.config)
|
||||
ensure_config_path(config_dir)
|
||||
|
||||
# os x launchd functions
|
||||
# OS X launchd functions
|
||||
if args.install_osx:
|
||||
install_osx()
|
||||
return 0
|
||||
@@ -305,7 +311,7 @@ def main():
|
||||
install_osx()
|
||||
return 0
|
||||
|
||||
# daemon functions
|
||||
# Daemon functions
|
||||
if args.pid_file:
|
||||
check_pid(args.pid_file)
|
||||
if args.daemon:
|
||||
|
||||
+14
-16
@@ -34,8 +34,7 @@ ERROR_LOG_FILENAME = 'home-assistant.log'
|
||||
|
||||
|
||||
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:
|
||||
return True
|
||||
|
||||
@@ -58,7 +57,7 @@ def setup_component(hass, domain, config=None):
|
||||
|
||||
|
||||
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'):
|
||||
return True
|
||||
|
||||
@@ -126,7 +125,7 @@ def _setup_component(hass, domain, config):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
""" Add local library to Python Path """
|
||||
"""Add local library to Python Path."""
|
||||
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,
|
||||
verbose=False, daemon=False, skip_pip=False,
|
||||
log_rotate_days=None):
|
||||
"""
|
||||
Tries to configure Home Assistant from a config dict.
|
||||
"""Try to configure Home Assistant from a config dict.
|
||||
|
||||
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')
|
||||
|
||||
# give event decorators access to HASS
|
||||
# Give event decorators access to HASS
|
||||
event_decorators.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,
|
||||
skip_pip=True, log_rotate_days=None):
|
||||
"""
|
||||
Reads the configuration file and tries to start all the required
|
||||
functionality. Will add functionality to 'hass' parameter if given,
|
||||
"""Read the configuration file and try to start all the functionality.
|
||||
|
||||
Will add functionality to 'hass' parameter if given,
|
||||
instantiates a new Home Assistant object if 'hass' is not given.
|
||||
"""
|
||||
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):
|
||||
""" Setup the logging for home assistant. """
|
||||
"""Setup the logging."""
|
||||
if not daemon:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
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):
|
||||
""" Upgrade config if necessary. """
|
||||
"""Upgrade config if necessary."""
|
||||
version_path = hass.config.path('.HA_VERSION')
|
||||
|
||||
try:
|
||||
@@ -322,11 +320,11 @@ def process_ha_config_upgrade(hass):
|
||||
|
||||
|
||||
def process_ha_core_config(hass, config):
|
||||
""" Processes the [homeassistant] section from the config. """
|
||||
"""Process the [homeassistant] section from the config."""
|
||||
hac = hass.config
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
@@ -397,6 +395,6 @@ def process_ha_core_config(hass, config):
|
||||
|
||||
|
||||
def _ensure_loader_prepared(hass):
|
||||
""" Ensure Home Assistant loader is prepared. """
|
||||
"""Ensure Home Assistant loader is prepared."""
|
||||
if not loader.PREPARED:
|
||||
loader.prepare(hass)
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
"""
|
||||
homeassistant.components
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
This package contains components that can be plugged into Home Assistant.
|
||||
|
||||
Component design guidelines:
|
||||
|
||||
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 should publish services only under its own domain.
|
||||
- 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 should publish services only under its own domain.
|
||||
"""
|
||||
import itertools as it
|
||||
import logging
|
||||
@@ -26,8 +21,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_on(hass, entity_id=None):
|
||||
""" Loads up the module to call the is_on method.
|
||||
If there is no entity id given we will check all. """
|
||||
"""Load up the module to call the is_on method.
|
||||
|
||||
If there is no entity id given we will check all.
|
||||
"""
|
||||
if entity_id:
|
||||
group = get_component('group')
|
||||
|
||||
@@ -53,7 +50,7 @@ def is_on(hass, entity_id=None):
|
||||
|
||||
|
||||
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:
|
||||
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):
|
||||
""" Turns specified entity off. """
|
||||
"""Turn specified entity off."""
|
||||
if entity_id is not None:
|
||||
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):
|
||||
""" Toggles specified entity. """
|
||||
"""Toggle specified entity."""
|
||||
if entity_id is not None:
|
||||
service_data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
@@ -77,10 +74,9 @@ def toggle(hass, entity_id=None, **service_data):
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup general services related to homeassistant. """
|
||||
|
||||
"""Setup general services related to Home Assistant."""
|
||||
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)
|
||||
|
||||
# Generic turn on/off method requires entity id
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
"""
|
||||
homeassistant.components.alarm_control_panel
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Component to interface with a alarm control panel.
|
||||
Component to interface with an 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 os
|
||||
|
||||
from homeassistant.components import verisure
|
||||
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)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -31,9 +32,6 @@ SERVICE_TO_METHOD = {
|
||||
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
|
||||
}
|
||||
|
||||
ATTR_CODE = 'code'
|
||||
ATTR_CODE_FORMAT = 'code_format'
|
||||
|
||||
ATTR_TO_PROPERTY = [
|
||||
ATTR_CODE,
|
||||
ATTR_CODE_FORMAT
|
||||
@@ -41,7 +39,7 @@ ATTR_TO_PROPERTY = [
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Track states and offer events for sensors. """
|
||||
"""Track states and offer events for sensors."""
|
||||
component = EntityComponent(
|
||||
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
|
||||
DISCOVERY_PLATFORMS)
|
||||
@@ -49,7 +47,7 @@ def setup(hass, config):
|
||||
component.setup(config)
|
||||
|
||||
def alarm_service_handler(service):
|
||||
""" Maps services to methods on Alarm. """
|
||||
"""Map services to methods on Alarm."""
|
||||
target_alarms = component.extract_from_service(service)
|
||||
|
||||
if ATTR_CODE not in service.data:
|
||||
@@ -75,7 +73,7 @@ def setup(hass, config):
|
||||
|
||||
|
||||
def alarm_disarm(hass, code=None, entity_id=None):
|
||||
""" Send the alarm the command for disarm. """
|
||||
"""Send the alarm the command for disarm."""
|
||||
data = {}
|
||||
if 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):
|
||||
""" Send the alarm the command for arm home. """
|
||||
"""Send the alarm the command for arm home."""
|
||||
data = {}
|
||||
if 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):
|
||||
""" Send the alarm the command for arm away. """
|
||||
"""Send the alarm the command for arm away."""
|
||||
data = {}
|
||||
if 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):
|
||||
""" Send the alarm the command for trigger. """
|
||||
"""Send the alarm the command for trigger."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
@@ -120,33 +118,33 @@ def alarm_trigger(hass, code=None, entity_id=None):
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
class AlarmControlPanel(Entity):
|
||||
""" ABC for alarm control devices. """
|
||||
"""An abstract class for alarm control devices."""
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
"""Send disarm command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
"""Send arm home command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
"""Send arm away command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
""" Send alarm trigger command. """
|
||||
"""Send alarm trigger command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Return the state attributes. """
|
||||
"""Return the state attributes."""
|
||||
state_attr = {
|
||||
ATTR_CODE_FORMAT: self.code_format,
|
||||
}
|
||||
}
|
||||
return state_attr
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.alarm_control_panel.alarmdotcom
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Interfaces with Verisure alarm control panel.
|
||||
Interfaces with Alarm.com alarm control panels.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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):
|
||||
""" Setup an Alarm.com control panel. """
|
||||
|
||||
"""Setup an Alarm.com control panel."""
|
||||
username = config.get(CONF_USERNAME)
|
||||
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=abstract-method
|
||||
class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
""" Represents a Alarm.com status. """
|
||||
"""Represent an Alarm.com status."""
|
||||
|
||||
def __init__(self, hass, name, code, username, password):
|
||||
"""Initialize the Alarm.com status."""
|
||||
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
|
||||
self._alarm = Alarmdotcom(username, password, timeout=10)
|
||||
self._hass = hass
|
||||
@@ -55,22 +53,22 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed. """
|
||||
"""No polling needed."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
"""Return the name of the alarm."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
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 '.+'
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
"""Return the state of the device."""
|
||||
if self._alarm.state == 'Disarmed':
|
||||
return STATE_ALARM_DISARMED
|
||||
elif self._alarm.state == 'Armed Stay':
|
||||
@@ -81,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
return STATE_UNKNOWN
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
|
||||
@@ -90,7 +88,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
_alarm.disarm()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
|
||||
@@ -99,7 +97,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
_alarm.arm_stay()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
|
||||
@@ -108,7 +106,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
_alarm.arm_away()
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
""" Validate given code. """
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning('Wrong code entered for %s', state)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.alarm_control_panel.manual
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for manual alarms.
|
||||
|
||||
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):
|
||||
""" Sets up the manual alarm platform. """
|
||||
|
||||
"""Setup the manual alarm platform."""
|
||||
add_devices([ManualAlarm(
|
||||
hass,
|
||||
config.get('name', DEFAULT_ALARM_NAME),
|
||||
@@ -47,6 +44,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
"""
|
||||
|
||||
def __init__(self, hass, name, code, pending_time, trigger_time):
|
||||
"""Initalize the manual alarm panel."""
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
@@ -57,17 +55,17 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed. """
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
"""Return the state of the device."""
|
||||
if self._state in (STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY) and \
|
||||
self._pending_time and self._state_ts + self._pending_time > \
|
||||
@@ -85,11 +83,11 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
""" One or more characters. """
|
||||
"""One or more characters."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, STATE_ALARM_DISARMED):
|
||||
return
|
||||
|
||||
@@ -98,7 +96,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self.update_ha_state()
|
||||
|
||||
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):
|
||||
return
|
||||
|
||||
@@ -112,7 +110,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
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):
|
||||
return
|
||||
|
||||
@@ -126,7 +124,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
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_ts = dt_util.utcnow()
|
||||
self.update_ha_state()
|
||||
@@ -141,7 +139,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._state_ts + self._pending_time + self._trigger_time)
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
""" Validate given code. """
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning('Invalid code given for %s', state)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.alarm_control_panel.mqtt
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
This platform enables the possibility to control a MQTT alarm.
|
||||
|
||||
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):
|
||||
""" Sets up the MQTT platform. """
|
||||
|
||||
"""Setup the MQTT platform."""
|
||||
if config.get('state_topic') is None:
|
||||
_LOGGER.error("Missing required variable: state_topic")
|
||||
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=abstract-method
|
||||
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,
|
||||
payload_disarm, payload_arm_home, payload_arm_away, code):
|
||||
"""Initalize the MQTT alarm panel."""
|
||||
self._state = STATE_UNKNOWN
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
@@ -67,7 +65,7 @@ class MqttAlarm(alarm.AlarmControlPanel):
|
||||
self._code = str(code) if code else None
|
||||
|
||||
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,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING,
|
||||
STATE_ALARM_TRIGGERED):
|
||||
@@ -80,47 +78,47 @@ class MqttAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed """
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
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 '.+'
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
mqtt.publish(self.hass, self._command_topic,
|
||||
self._payload_disarm, self._qos)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
mqtt.publish(self.hass, self._command_topic,
|
||||
self._payload_arm_home, self._qos)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
mqtt.publish(self.hass, self._command_topic,
|
||||
self._payload_arm_away, self._qos)
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
""" Validate given code. """
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning('Wrong code entered for %s', state)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.alarm_control_panel.nx584
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for NX584 alarm control panels.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
@@ -16,12 +14,11 @@ from homeassistant.const import (
|
||||
STATE_UNKNOWN)
|
||||
|
||||
REQUIREMENTS = ['pynx584==0.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Setup nx584. """
|
||||
"""Setup nx584 platform."""
|
||||
host = config.get('host', 'localhost:5007')
|
||||
|
||||
try:
|
||||
@@ -32,8 +29,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class NX584Alarm(alarm.AlarmControlPanel):
|
||||
""" NX584-based alarm panel. """
|
||||
"""Represents the NX584-based alarm panel."""
|
||||
|
||||
def __init__(self, hass, host, name):
|
||||
"""Initalize the nx584 alarm panel."""
|
||||
from nx584 import client
|
||||
self._hass = hass
|
||||
self._host = host
|
||||
@@ -46,22 +45,22 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Polling needed. """
|
||||
"""Polling needed."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
""" Characters if code is defined. """
|
||||
"""The characters if code is defined."""
|
||||
return '[0-9]{4}([0-9]{2})?'
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
"""Return the state of the device."""
|
||||
try:
|
||||
part = self._alarm.list_partitions()[0]
|
||||
zones = self._alarm.list_zones()
|
||||
@@ -90,17 +89,17 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
"""Send disarm command."""
|
||||
self._alarm.disarm(code)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
"""Send arm home command."""
|
||||
self._alarm.arm('home')
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
"""Send arm away command."""
|
||||
self._alarm.arm('auto')
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
""" Alarm trigger command. """
|
||||
"""Alarm trigger command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""
|
||||
homeassistant.components.alarm_control_panel.verisure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Interfaces with Verisure alarm control panel.
|
||||
|
||||
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
|
||||
|
||||
@@ -19,8 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Verisure platform. """
|
||||
|
||||
"""Setup the Verisure platform."""
|
||||
alarms = []
|
||||
if int(hub.config.get('alarm', '1')):
|
||||
hub.update_alarms()
|
||||
@@ -33,30 +30,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
""" Represents a Verisure alarm status. """
|
||||
"""Represent a Verisure alarm status."""
|
||||
|
||||
def __init__(self, device_id):
|
||||
"""Initalize the Verisure alarm panel."""
|
||||
self._id = device_id
|
||||
self._state = STATE_UNKNOWN
|
||||
self._digits = int(hub.config.get('code_digits', '4'))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
"""Return the name of the device."""
|
||||
return 'Alarm {}'.format(self._id)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
""" code format as regex """
|
||||
"""The code format as regex."""
|
||||
return '^\\d{%s}$' % self._digits
|
||||
|
||||
def update(self):
|
||||
""" Update alarm status """
|
||||
"""Update alarm status."""
|
||||
hub.update_alarms()
|
||||
|
||||
if hub.alarm_status[self._id].status == 'unarmed':
|
||||
@@ -71,21 +69,21 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
hub.alarm_status[self._id].status)
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
"""Send disarm command."""
|
||||
hub.my_pages.alarm.set(code, 'DISARMED')
|
||||
_LOGGER.info('verisure alarm disarming')
|
||||
hub.my_pages.alarm.wait_while_pending()
|
||||
self.update()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
"""Send arm home command."""
|
||||
hub.my_pages.alarm.set(code, 'ARMED_HOME')
|
||||
_LOGGER.info('verisure alarm arming home')
|
||||
hub.my_pages.alarm.wait_while_pending()
|
||||
self.update()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
"""Send arm away command."""
|
||||
hub.my_pages.alarm.set(code, 'ARMED_AWAY')
|
||||
_LOGGER.info('verisure alarm arming away')
|
||||
hub.my_pages.alarm.wait_while_pending()
|
||||
|
||||
@@ -97,21 +97,24 @@ def _handle_alexa(handler, path_match, data):
|
||||
|
||||
|
||||
class SpeechType(enum.Enum):
|
||||
"""Alexa speech types."""
|
||||
"""The Alexa speech types."""
|
||||
|
||||
plaintext = "PlainText"
|
||||
ssml = "SSML"
|
||||
|
||||
|
||||
class CardType(enum.Enum):
|
||||
"""Alexa card types."""
|
||||
"""The Alexa card types."""
|
||||
|
||||
simple = "Simple"
|
||||
link_account = "LinkAccount"
|
||||
|
||||
|
||||
class AlexaResponse(object):
|
||||
"""Helps generating the response for Alexa."""
|
||||
"""Help generating the response for Alexa."""
|
||||
|
||||
def __init__(self, hass, intent=None):
|
||||
"""Initialize the response."""
|
||||
self.hass = hass
|
||||
self.speech = None
|
||||
self.card = None
|
||||
@@ -125,7 +128,7 @@ class AlexaResponse(object):
|
||||
self.variables = {}
|
||||
|
||||
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
|
||||
|
||||
card = {
|
||||
@@ -141,7 +144,7 @@ class AlexaResponse(object):
|
||||
self.card = card
|
||||
|
||||
def add_speech(self, speech_type, text):
|
||||
""" Add speech to the response. """
|
||||
"""Add speech to the response."""
|
||||
assert self.speech is None
|
||||
|
||||
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
|
||||
@@ -163,7 +166,7 @@ class AlexaResponse(object):
|
||||
}
|
||||
|
||||
def as_dict(self):
|
||||
"""Returns response in an Alexa valid dict."""
|
||||
"""Return response in an Alexa valid dict."""
|
||||
response = {
|
||||
'shouldEndSession': self.should_end_session
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.apcupsd
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets up and provides access to the status output of APCUPSd via its Network
|
||||
Information Server (NIS).
|
||||
Support for status output of APCUPSd via its Network Information Server (NIS).
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/apcupsd/
|
||||
@@ -34,7 +31,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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
|
||||
|
||||
host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST)
|
||||
@@ -54,11 +51,14 @@ def setup(hass, config):
|
||||
|
||||
|
||||
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):
|
||||
"""Initialize the data oject."""
|
||||
from apcaccess import status
|
||||
self._host = host
|
||||
self._port = port
|
||||
@@ -68,17 +68,15 @@ class APCUPSdData(object):
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
""" Get latest update if throttle allows. Return status. """
|
||||
"""Get latest update if throttle allows. Return status."""
|
||||
self.update()
|
||||
return self._status
|
||||
|
||||
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))
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self, **kwargs):
|
||||
"""
|
||||
Fetch the latest status from APCUPSd and store it in self._status.
|
||||
"""
|
||||
"""Fetch the latest status from APCUPSd."""
|
||||
self._status = self._get_status()
|
||||
|
||||
@@ -34,7 +34,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def setup(hass, config):
|
||||
"""Register the API with the HTTP interface."""
|
||||
|
||||
# /api - for validation purposes
|
||||
hass.http.register_path('GET', URL_API, _handle_get_api)
|
||||
|
||||
@@ -84,11 +83,13 @@ def setup(hass, config):
|
||||
hass.http.register_path(
|
||||
'GET', URL_API_COMPONENTS, _handle_get_api_components)
|
||||
|
||||
# /error_log
|
||||
hass.http.register_path('GET', URL_API_ERROR_LOG,
|
||||
_handle_get_api_error_log)
|
||||
|
||||
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
|
||||
|
||||
# /template
|
||||
hass.http.register_path('POST', URL_API_TEMPLATE,
|
||||
_handle_post_api_template)
|
||||
|
||||
@@ -96,7 +97,7 @@ def setup(hass, config):
|
||||
|
||||
|
||||
def _handle_get_api(handler, path_match, data):
|
||||
"""Renders the debug interface."""
|
||||
"""Render the debug interface."""
|
||||
handler.write_json_message("API running.")
|
||||
|
||||
|
||||
@@ -114,7 +115,7 @@ def _handle_get_api_stream(handler, path_match, data):
|
||||
restrict = restrict.split(',')
|
||||
|
||||
def write_message(payload):
|
||||
"""Writes a message to the output."""
|
||||
"""Write a message to the output."""
|
||||
with write_lock:
|
||||
msg = "data: {}\n\n".format(payload)
|
||||
|
||||
@@ -127,7 +128,7 @@ def _handle_get_api_stream(handler, path_match, data):
|
||||
block.set()
|
||||
|
||||
def forward_events(event):
|
||||
"""Forwards events to the open request."""
|
||||
"""Forward events to the open request."""
|
||||
nonlocal gracefully_closed
|
||||
|
||||
if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
|
||||
@@ -171,17 +172,17 @@ def _handle_get_api_stream(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())
|
||||
|
||||
|
||||
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())
|
||||
|
||||
|
||||
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')
|
||||
|
||||
state = handler.server.hass.states.get(entity_id)
|
||||
@@ -193,7 +194,7 @@ def _handle_get_api_states_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:
|
||||
/api/states/<entity_id>
|
||||
@@ -240,15 +241,14 @@ def _handle_delete_state_entity(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))
|
||||
|
||||
|
||||
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:
|
||||
/api/events/<event_type>
|
||||
This handles the following paths: /api/events/<event_type>
|
||||
|
||||
Events from /api are threated as remote events.
|
||||
"""
|
||||
@@ -276,16 +276,15 @@ def _handle_api_post_events_event(handler, path_match, event_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))
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def _handle_post_api_services_domain_service(handler, path_match, data):
|
||||
"""Handles calling a service.
|
||||
"""Handle calling a service.
|
||||
|
||||
This handles the following paths:
|
||||
/api/services/<domain>/<service>
|
||||
This handles the following paths: /api/services/<domain>/<service>
|
||||
"""
|
||||
domain = path_match.group('domain')
|
||||
service = path_match.group('service')
|
||||
@@ -298,7 +297,7 @@ def _handle_post_api_services_domain_service(handler, path_match, data):
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def _handle_post_api_event_forward(handler, path_match, data):
|
||||
"""Handles adding an event forwarding target."""
|
||||
"""Handle adding an event forwarding target."""
|
||||
try:
|
||||
host = data['host']
|
||||
api_password = data['api_password']
|
||||
@@ -331,7 +330,7 @@ def _handle_post_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:
|
||||
host = data['host']
|
||||
except KeyError:
|
||||
@@ -354,12 +353,12 @@ def _handle_delete_api_event_forward(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)
|
||||
|
||||
|
||||
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),
|
||||
False)
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
components.arduino
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Arduino component that connects to a directly attached Arduino board which
|
||||
runs with the Firmata firmware.
|
||||
Support for Arduino boards running with the Firmata firmware.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/arduino/
|
||||
@@ -20,8 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup the Arduino component. """
|
||||
|
||||
"""Setup the Arduino component."""
|
||||
if not validate_config(config,
|
||||
{DOMAIN: ['port']},
|
||||
_LOGGER):
|
||||
@@ -40,11 +36,11 @@ def setup(hass, config):
|
||||
return False
|
||||
|
||||
def stop_arduino(event):
|
||||
""" Stop the Arduino service. """
|
||||
"""Stop the Arduino service."""
|
||||
BOARD.disconnect()
|
||||
|
||||
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_START, start_arduino)
|
||||
@@ -53,15 +49,16 @@ def setup(hass, config):
|
||||
|
||||
|
||||
class ArduinoBoard(object):
|
||||
""" Represents an Arduino board. """
|
||||
"""Representation of an Arduino board."""
|
||||
|
||||
def __init__(self, port):
|
||||
"""Initialize the board."""
|
||||
from PyMata.pymata import PyMata
|
||||
self._port = port
|
||||
self._board = PyMata(self._port, verbose=False)
|
||||
|
||||
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':
|
||||
self._board.set_pin_mode(pin,
|
||||
self._board.INPUT,
|
||||
@@ -84,31 +81,31 @@ class ArduinoBoard(object):
|
||||
self._board.PWM)
|
||||
|
||||
def get_analog_inputs(self):
|
||||
""" Get the values from the pins. """
|
||||
"""Get the values from the pins."""
|
||||
self._board.capability_query()
|
||||
return self._board.get_analog_response_table()
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def get_firmata(self):
|
||||
""" Return the version of the Firmata firmware. """
|
||||
"""Return the version of the Firmata firmware."""
|
||||
return self._board.get_firmata_version()
|
||||
|
||||
def disconnect(self):
|
||||
""" Disconnects the board and closes the serial connection. """
|
||||
"""Disconnect the board and close the serial connection."""
|
||||
self._board.reset()
|
||||
self._board.close()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Allows to setup simple automation rules via the config file.
|
||||
Allow to setup simple automation rules via the config file.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/automation/
|
||||
@@ -11,14 +9,16 @@ import logging
|
||||
from homeassistant.bootstrap import prepare_setup_platform
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.components import logbook
|
||||
from homeassistant.helpers.service import call_from_config
|
||||
from homeassistant.helpers import extract_domain_configs
|
||||
from homeassistant.helpers.service import (call_from_config,
|
||||
validate_service_call)
|
||||
|
||||
|
||||
DOMAIN = 'automation'
|
||||
|
||||
DEPENDENCIES = ['group']
|
||||
|
||||
CONF_ALIAS = 'alias'
|
||||
CONF_SERVICE = 'service'
|
||||
|
||||
CONF_CONDITION = 'condition'
|
||||
CONF_ACTION = 'action'
|
||||
@@ -35,37 +35,23 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Sets up automation. """
|
||||
config_key = DOMAIN
|
||||
found = 1
|
||||
"""Setup the automation."""
|
||||
for config_key in extract_domain_configs(config, DOMAIN):
|
||||
conf = config[config_key]
|
||||
|
||||
while config_key in config:
|
||||
# check for one block syntax
|
||||
if isinstance(config[config_key], dict):
|
||||
config_block = _migrate_old_config(config[config_key])
|
||||
name = config_block.get(CONF_ALIAS, config_key)
|
||||
if not isinstance(conf, list):
|
||||
conf = [conf]
|
||||
|
||||
for list_no, config_block in enumerate(conf):
|
||||
name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key,
|
||||
list_no))
|
||||
_setup_automation(hass, config_block, name, config)
|
||||
|
||||
# check for multiple block syntax
|
||||
elif isinstance(config[config_key], list):
|
||||
for list_no, config_block in enumerate(config[config_key]):
|
||||
name = config_block.get(CONF_ALIAS,
|
||||
"{}, {}".format(config_key, list_no))
|
||||
_setup_automation(hass, config_block, name, config)
|
||||
|
||||
# any scalar value is incorrect
|
||||
else:
|
||||
_LOGGER.error('Error in config in section %s.', config_key)
|
||||
|
||||
found += 1
|
||||
config_key = "{} {}".format(DOMAIN, found)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if action is None:
|
||||
@@ -83,14 +69,14 @@ def _setup_automation(hass, config_block, name, config):
|
||||
|
||||
|
||||
def _get_action(hass, config, name):
|
||||
""" Return an action based on a config. """
|
||||
|
||||
if CONF_SERVICE not in config:
|
||||
_LOGGER.error('Error setting up %s, no action specified.', name)
|
||||
"""Return an action based on a configuration."""
|
||||
validation_error = validate_service_call(config)
|
||||
if validation_error:
|
||||
_LOGGER.error(validation_error)
|
||||
return None
|
||||
|
||||
def action():
|
||||
""" Action to be executed. """
|
||||
"""Action to be executed."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
|
||||
|
||||
@@ -99,43 +85,8 @@ def _get_action(hass, config, name):
|
||||
return action
|
||||
|
||||
|
||||
def _migrate_old_config(config):
|
||||
""" Migrate old config to new. """
|
||||
if CONF_PLATFORM not in config:
|
||||
return config
|
||||
|
||||
_LOGGER.warning(
|
||||
'You are using an old configuration format. Please upgrade: '
|
||||
'https://home-assistant.io/components/automation/')
|
||||
|
||||
new_conf = {
|
||||
CONF_TRIGGER: dict(config),
|
||||
CONF_CONDITION: config.get('if', []),
|
||||
CONF_ACTION: dict(config),
|
||||
}
|
||||
|
||||
for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'),
|
||||
('trigger', 'mqtt_payload', 'payload'),
|
||||
('trigger', 'state_entity_id', 'entity_id'),
|
||||
('trigger', 'state_before', 'before'),
|
||||
('trigger', 'state_after', 'after'),
|
||||
('trigger', 'state_to', 'to'),
|
||||
('trigger', 'state_from', 'from'),
|
||||
('trigger', 'state_hours', 'hours'),
|
||||
('trigger', 'state_minutes', 'minutes'),
|
||||
('trigger', 'state_seconds', 'seconds'),
|
||||
('action', 'execute_service', 'service'),
|
||||
('action', 'service_entity_id', 'entity_id'),
|
||||
('action', 'service_data', 'data')):
|
||||
if key in new_conf[cat]:
|
||||
new_conf[cat][new_key] = new_conf[cat].pop(key)
|
||||
|
||||
return new_conf
|
||||
|
||||
|
||||
def _process_if(hass, config, p_config, action):
|
||||
""" Processes if checks. """
|
||||
|
||||
"""Process if checks."""
|
||||
cond_type = p_config.get(CONF_CONDITION_TYPE,
|
||||
DEFAULT_CONDITION_TYPE).lower()
|
||||
|
||||
@@ -165,12 +116,12 @@ def _process_if(hass, config, p_config, action):
|
||||
|
||||
if cond_type == CONDITION_TYPE_AND:
|
||||
def if_action():
|
||||
""" AND all conditions. """
|
||||
"""AND all conditions."""
|
||||
if all(check() for check in checks):
|
||||
action()
|
||||
else:
|
||||
def if_action():
|
||||
""" OR all conditions. """
|
||||
"""OR all conditions."""
|
||||
if any(check() for check in checks):
|
||||
action()
|
||||
|
||||
@@ -178,7 +129,7 @@ def _process_if(hass, config, p_config, action):
|
||||
|
||||
|
||||
def _process_trigger(hass, config, trigger_configs, name, action):
|
||||
""" Setup triggers. """
|
||||
"""Setup the triggers."""
|
||||
if isinstance(trigger_configs, dict):
|
||||
trigger_configs = [trigger_configs]
|
||||
|
||||
@@ -195,7 +146,7 @@ def _process_trigger(hass, config, trigger_configs, name, action):
|
||||
|
||||
|
||||
def _resolve_platform(method, hass, config, platform):
|
||||
""" Find automation platform. """
|
||||
"""Find the automation platform."""
|
||||
if platform is None:
|
||||
return None
|
||||
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.event
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers event listening automation rules.
|
||||
Offer event listening automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#event-trigger
|
||||
@@ -15,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def trigger(hass, config, action):
|
||||
""" Listen for events based on config. """
|
||||
"""Listen for events based on configuration."""
|
||||
event_type = config.get(CONF_EVENT_TYPE)
|
||||
|
||||
if event_type is None:
|
||||
@@ -25,7 +23,7 @@ def trigger(hass, config, action):
|
||||
event_data = config.get(CONF_EVENT_DATA)
|
||||
|
||||
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
|
||||
in event_data.items()):
|
||||
action()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.mqtt
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers MQTT listening automation rules.
|
||||
Offer MQTT listening automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#mqtt-trigger
|
||||
@@ -17,7 +15,7 @@ CONF_PAYLOAD = 'payload'
|
||||
|
||||
|
||||
def trigger(hass, config, action):
|
||||
""" Listen for state changes based on `config`. """
|
||||
"""Listen for state changes based on configuration."""
|
||||
topic = config.get(CONF_TOPIC)
|
||||
payload = config.get(CONF_PAYLOAD)
|
||||
|
||||
@@ -27,7 +25,7 @@ def trigger(hass, config, action):
|
||||
return False
|
||||
|
||||
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:
|
||||
action()
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.numeric_state
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers numeric state listening automation rules.
|
||||
Offer numeric state listening automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#numeric-state-trigger
|
||||
@@ -21,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _renderer(hass, value_template, state):
|
||||
"""Render state value."""
|
||||
"""Render the state value."""
|
||||
if value_template is None:
|
||||
return state.state
|
||||
|
||||
@@ -29,7 +27,7 @@ def _renderer(hass, value_template, state):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if entity_id is None:
|
||||
@@ -50,8 +48,7 @@ def trigger(hass, config, action):
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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
|
||||
if _in_range(above, below, renderer(to_s)) and \
|
||||
(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):
|
||||
""" Wraps action method with state based condition. """
|
||||
|
||||
"""Wrap action method with state based condition."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
|
||||
if entity_id is None:
|
||||
@@ -85,7 +81,7 @@ def if_action(hass, config):
|
||||
renderer = partial(_renderer, hass, value_template)
|
||||
|
||||
def if_numeric_state():
|
||||
""" Test numeric state condition. """
|
||||
"""Test numeric state condition."""
|
||||
state = hass.states.get(entity_id)
|
||||
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):
|
||||
""" Checks if value is inside the range """
|
||||
"""Check if value is inside the range."""
|
||||
try:
|
||||
value = float(value)
|
||||
except ValueError:
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.state
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers state listening automation rules.
|
||||
Offer state listening automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#state-trigger
|
||||
@@ -25,7 +23,7 @@ CONF_FOR = "for"
|
||||
|
||||
|
||||
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:
|
||||
return None
|
||||
|
||||
@@ -51,7 +49,7 @@ def get_time_config(config):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if entity_id is None:
|
||||
@@ -72,17 +70,15 @@ def trigger(hass, config, action):
|
||||
return None
|
||||
|
||||
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):
|
||||
""" Fires on state changes after a delay and calls action. """
|
||||
"""Fire on state changes after a delay and calls action."""
|
||||
hass.bus.remove_listener(
|
||||
EVENT_STATE_CHANGED, for_state_listener)
|
||||
action()
|
||||
|
||||
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
|
||||
""" Fires on state changes and cancels
|
||||
for listener if state changed. """
|
||||
"""Fire on changes and cancel for listener if changed."""
|
||||
if inner_to_s == to_s:
|
||||
return
|
||||
hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener)
|
||||
@@ -106,7 +102,7 @@ def trigger(hass, config, action):
|
||||
|
||||
|
||||
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)
|
||||
state = config.get(CONF_STATE)
|
||||
|
||||
@@ -123,7 +119,7 @@ def if_action(hass, config):
|
||||
state = str(state)
|
||||
|
||||
def if_state():
|
||||
""" Test if condition. """
|
||||
"""Test if condition."""
|
||||
is_state = hass.states.is_state(entity_id, state)
|
||||
return (time_delta is None and is_state or
|
||||
time_delta is not None and
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.sun
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers sun based automation rules.
|
||||
Offer sun based automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#sun-trigger
|
||||
@@ -29,7 +27,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def trigger(hass, config, action):
|
||||
""" Listen for events based on config. """
|
||||
"""Listen for events based on configuration."""
|
||||
event = config.get(CONF_EVENT)
|
||||
|
||||
if event is None:
|
||||
@@ -55,7 +53,7 @@ def trigger(hass, config, action):
|
||||
|
||||
|
||||
def if_action(hass, config):
|
||||
""" Wraps action method with sun based condition. """
|
||||
"""Wrap action method with sun based condition."""
|
||||
before = config.get(CONF_BEFORE)
|
||||
after = config.get(CONF_AFTER)
|
||||
|
||||
@@ -106,8 +104,7 @@ def if_action(hass, config):
|
||||
return sun.next_setting(hass) + after_offset
|
||||
|
||||
def time_if():
|
||||
""" Validate time based if-condition """
|
||||
|
||||
"""Validate time based if-condition."""
|
||||
now = dt_util.now()
|
||||
before = before_func()
|
||||
after = after_func()
|
||||
@@ -126,6 +123,7 @@ def if_action(hass, config):
|
||||
|
||||
|
||||
def _parse_offset(raw_offset):
|
||||
"""Parse the offset."""
|
||||
if raw_offset is None:
|
||||
return timedelta(0)
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.template
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers template automation rules.
|
||||
Offer template automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#template-trigger
|
||||
@@ -16,7 +14,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if value_template is None:
|
||||
@@ -27,7 +25,7 @@ def trigger(hass, config, action):
|
||||
already_triggered = False
|
||||
|
||||
def event_listener(event):
|
||||
""" Listens for state changes and calls action. """
|
||||
"""Listen for state changes and calls action."""
|
||||
nonlocal already_triggered
|
||||
template_result = _check_template(hass, value_template)
|
||||
|
||||
@@ -43,8 +41,7 @@ def trigger(hass, config, action):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if value_template is None:
|
||||
@@ -55,11 +52,16 @@ def if_action(hass, config):
|
||||
|
||||
|
||||
def _check_template(hass, value_template):
|
||||
""" Checks if result of template is true """
|
||||
"""Check if result of template is true."""
|
||||
try:
|
||||
value = template.render(hass, value_template, {})
|
||||
except TemplateError:
|
||||
_LOGGER.exception('Error parsing template')
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
# Common during HA startup - so just a warning
|
||||
_LOGGER.warning(ex)
|
||||
else:
|
||||
_LOGGER.error(ex)
|
||||
return False
|
||||
|
||||
return value.lower() == 'true'
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.time
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers time listening automation rules.
|
||||
Offer time listening automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#time-trigger
|
||||
@@ -24,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def trigger(hass, config, action):
|
||||
""" Listen for state changes based on `config`. """
|
||||
"""Listen for state changes based on configuration."""
|
||||
if CONF_AFTER in config:
|
||||
after = dt_util.parse_time_str(config[CONF_AFTER])
|
||||
if after is None:
|
||||
@@ -42,7 +40,7 @@ def trigger(hass, config, action):
|
||||
return False
|
||||
|
||||
def time_automation_listener(now):
|
||||
""" Listens for time changes and calls action. """
|
||||
"""Listen for time changes and calls action."""
|
||||
action()
|
||||
|
||||
track_time_change(hass, time_automation_listener,
|
||||
@@ -52,7 +50,7 @@ def trigger(hass, config, action):
|
||||
|
||||
|
||||
def if_action(hass, config):
|
||||
""" Wraps action method with time based condition. """
|
||||
"""Wrap action method with time based condition."""
|
||||
before = config.get(CONF_BEFORE)
|
||||
after = config.get(CONF_AFTER)
|
||||
weekday = config.get(CONF_WEEKDAY)
|
||||
@@ -76,7 +74,7 @@ def if_action(hass, config):
|
||||
return None
|
||||
|
||||
def time_if():
|
||||
""" Validate time based if-condition """
|
||||
"""Validate time based if-condition."""
|
||||
now = dt_util.now()
|
||||
if before is not None and now > now.replace(hour=before.hour,
|
||||
minute=before.minute):
|
||||
@@ -99,7 +97,7 @@ def if_action(hass, config):
|
||||
|
||||
|
||||
def _error_time(value, key):
|
||||
""" Helper method to print error. """
|
||||
"""Helper method to print error."""
|
||||
_LOGGER.error(
|
||||
"Received invalid value for '%s': %s", key, value)
|
||||
if isinstance(value, int):
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.automation.zone
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Offers zone automation rules.
|
||||
Offer zone automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#zone-trigger
|
||||
@@ -22,7 +20,7 @@ DEFAULT_EVENT = EVENT_ENTER
|
||||
|
||||
|
||||
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)
|
||||
zone_entity_id = config.get(CONF_ZONE)
|
||||
|
||||
@@ -35,7 +33,7 @@ def trigger(hass, config, action):
|
||||
event = config.get(CONF_EVENT, DEFAULT_EVENT)
|
||||
|
||||
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),
|
||||
from_s.attributes.get(ATTR_LONGITUDE)) or \
|
||||
None in (to_s.attributes.get(ATTR_LATITUDE),
|
||||
@@ -57,7 +55,7 @@ def trigger(hass, config, action):
|
||||
|
||||
|
||||
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)
|
||||
zone_entity_id = config.get(CONF_ZONE)
|
||||
|
||||
@@ -68,14 +66,14 @@ def if_action(hass, config):
|
||||
return False
|
||||
|
||||
def if_in_zone():
|
||||
""" Test if condition. """
|
||||
"""Test if condition."""
|
||||
return _in_zone(hass, zone_entity_id, hass.states.get(entity_id))
|
||||
|
||||
return if_in_zone
|
||||
|
||||
|
||||
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),
|
||||
state.attributes.get(ATTR_LONGITUDE)):
|
||||
return False
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Component to interface with binary sensors (sensors which only know two states)
|
||||
that can be monitored.
|
||||
Component to interface with binary sensors.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor/
|
||||
@@ -10,7 +9,8 @@ import logging
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (STATE_ON, STATE_OFF)
|
||||
from homeassistant.components import (bloomsky, mysensors, zwave, wink)
|
||||
from homeassistant.components import (
|
||||
bloomsky, mysensors, zwave, vera, wemo, wink)
|
||||
|
||||
DOMAIN = 'binary_sensor'
|
||||
SCAN_INTERVAL = 30
|
||||
@@ -38,6 +38,8 @@ DISCOVERY_PLATFORMS = {
|
||||
bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky',
|
||||
mysensors.DISCOVER_BINARY_SENSORS: 'mysensors',
|
||||
zwave.DISCOVER_BINARY_SENSORS: 'zwave',
|
||||
vera.DISCOVER_BINARY_SENSORS: 'vera',
|
||||
wemo.DISCOVER_BINARY_SENSORS: 'wemo',
|
||||
wink.DISCOVER_BINARY_SENSORS: 'wink'
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
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):
|
||||
"""Binary sensor to represent UPS online status."""
|
||||
"""Represent UPS online status."""
|
||||
|
||||
def __init__(self, config, data):
|
||||
"""Initialize the APCUPSd device."""
|
||||
self._config = config
|
||||
self._data = data
|
||||
self._state = None
|
||||
@@ -26,17 +28,14 @@ class OnlineStatus(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
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)
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Get the status report from APCUPSd (or cache) and set this entity's
|
||||
state.
|
||||
"""
|
||||
"""Get the status report from APCUPSd and set this entity's state."""
|
||||
self._state = self._data.status[apcupsd.KEY_STATUS]
|
||||
|
||||
@@ -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
|
||||
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):
|
||||
"""Get the aREST binary sensor."""
|
||||
"""Setup the aREST binary sensor."""
|
||||
resource = config.get(CONF_RESOURCE)
|
||||
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
|
||||
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):
|
||||
"""Initialize the aREST device."""
|
||||
self.arest = arest
|
||||
self._resource = resource
|
||||
self._name = name
|
||||
@@ -70,30 +71,32 @@ class ArestBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the binary sensor."""
|
||||
"""Return the name of the binary sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
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'))
|
||||
|
||||
def update(self):
|
||||
"""Gets the latest data from aREST API."""
|
||||
"""Get the latest data from aREST API."""
|
||||
self.arest.update()
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class ArestData(object):
|
||||
"""Class for handling the data retrieval for pins."""
|
||||
|
||||
def __init__(self, resource, pin):
|
||||
"""Initialize the aREST data object."""
|
||||
self._resource = resource
|
||||
self._pin = pin
|
||||
self.data = {}
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Gets the latest data from aREST device."""
|
||||
"""Get the latest data from aREST device."""
|
||||
try:
|
||||
response = requests.get('{}/digital/{}'.format(
|
||||
self._resource, self._pin), timeout=10)
|
||||
|
||||
@@ -19,7 +19,7 @@ SENSOR_TYPES = {
|
||||
|
||||
|
||||
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__)
|
||||
bloomsky = get_component('bloomsky')
|
||||
sensors = config.get('monitored_conditions', SENSOR_TYPES)
|
||||
@@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
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):
|
||||
"""Initialize a BloomSky binary sensor."""
|
||||
@@ -53,7 +53,7 @@ class BloomSkySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Unique ID for this sensor."""
|
||||
"""Return the unique ID for this sensor."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
@@ -63,7 +63,7 @@ class BloomSkySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""If binary sensor is on."""
|
||||
"""Return true if binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Allows to configure custom shell commands to turn a value into a logical value
|
||||
for a binary sensor.
|
||||
Support for custom shell commands to to retrieve values.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.command/
|
||||
@@ -25,7 +24,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Add the Command Sensor."""
|
||||
"""Setup the Command Sensor."""
|
||||
if config.get('command') is None:
|
||||
_LOGGER.error('Missing required variable: "command"')
|
||||
return False
|
||||
@@ -44,11 +43,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
class CommandBinarySensor(BinarySensorDevice):
|
||||
"""
|
||||
Represents a binary sensor that is returning a value of a shell commands.
|
||||
"""
|
||||
"""Represent a command line binary sensor."""
|
||||
|
||||
def __init__(self, hass, data, name, payload_on,
|
||||
payload_off, value_template):
|
||||
"""Initialize the Command line binary sensor."""
|
||||
self._hass = hass
|
||||
self.data = data
|
||||
self._name = name
|
||||
@@ -60,16 +59,16 @@ class CommandBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the sensor."""
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Gets the latest data and updates the state."""
|
||||
"""Get the latest data and updates the state."""
|
||||
self.data.update()
|
||||
value = self.data.value
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
class DemoBinarySensor(BinarySensorDevice):
|
||||
"""A Demo binary sensor."""
|
||||
|
||||
def __init__(self, name, state, sensor_class):
|
||||
"""Initialize the demo sensor."""
|
||||
self._name = name
|
||||
self._state = state
|
||||
self._sensor_type = sensor_class
|
||||
|
||||
@@ -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
|
||||
https://home-assistant.io/components/binary_sensor.mqtt/
|
||||
@@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.mqtt/
|
||||
import logging
|
||||
|
||||
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.helpers import template
|
||||
|
||||
@@ -24,15 +25,20 @@ DEPENDENCIES = ['mqtt']
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Add MQTT binary sensor."""
|
||||
|
||||
if config.get('state_topic') is None:
|
||||
_LOGGER.error('Missing required variable: state_topic')
|
||||
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(
|
||||
hass,
|
||||
config.get('name', DEFAULT_NAME),
|
||||
config.get('state_topic', None),
|
||||
sensor_class,
|
||||
config.get('qos', DEFAULT_QOS),
|
||||
config.get('payload_on', DEFAULT_PAYLOAD_ON),
|
||||
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
|
||||
class MqttBinarySensor(BinarySensorDevice):
|
||||
"""Represents a binary sensor that is updated by MQTT."""
|
||||
def __init__(self, hass, name, state_topic, qos, payload_on, payload_off,
|
||||
value_template):
|
||||
"""Representation a binary sensor that is updated by MQTT."""
|
||||
|
||||
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._name = name
|
||||
self._state = False
|
||||
self._state_topic = state_topic
|
||||
self._sensor_class = sensor_class
|
||||
self._payload_on = payload_on
|
||||
self._payload_off = payload_off
|
||||
self._qos = qos
|
||||
@@ -73,10 +82,15 @@ class MqttBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the binary sensor."""
|
||||
"""Return the name of the binary sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def sensor_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return self._sensor_class
|
||||
|
||||
@@ -90,7 +90,7 @@ class MySensorsBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""MySensor gateway pushes its state to HA."""
|
||||
"""Mysensor gateway pushes its state to HA."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
||||
@@ -26,7 +26,6 @@ BINARY_TYPES = ['fan',
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Nest binary sensors."""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
for structure in nest.NEST.structures:
|
||||
|
||||
@@ -66,6 +66,7 @@ class NX584ZoneSensor(BinarySensorDevice):
|
||||
"""Represents a NX584 zone as a sensor."""
|
||||
|
||||
def __init__(self, zone, zone_type):
|
||||
"""Initialize the nx594 binary sensor."""
|
||||
self._zone = zone
|
||||
self._zone_type = zone_type
|
||||
|
||||
@@ -81,7 +82,7 @@ class NX584ZoneSensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the binary sensor."""
|
||||
"""Return the name of the binary sensor."""
|
||||
return self._zone['name']
|
||||
|
||||
@property
|
||||
@@ -95,6 +96,7 @@ class NX584Watcher(threading.Thread):
|
||||
"""Event listener thread to process NX584 events."""
|
||||
|
||||
def __init__(self, client, zone_sensors):
|
||||
"""Initialize nx584 watcher thread."""
|
||||
super(NX584Watcher, self).__init__()
|
||||
self.daemon = True
|
||||
self._client = client
|
||||
@@ -115,7 +117,7 @@ class NX584Watcher(threading.Thread):
|
||||
self._process_zone_event(event)
|
||||
|
||||
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()
|
||||
while True:
|
||||
events = self._client.get_events()
|
||||
@@ -123,6 +125,7 @@ class NX584Watcher(threading.Thread):
|
||||
self._process_events(events)
|
||||
|
||||
def run(self):
|
||||
"""Run the watcher."""
|
||||
while True:
|
||||
try:
|
||||
self._run()
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"""
|
||||
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
|
||||
https://home-assistant.io/components/binary_sensor.rest/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
||||
SENSOR_CLASSES)
|
||||
from homeassistant.components.sensor.rest import RestData
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||
from homeassistant.helpers import template
|
||||
@@ -19,12 +20,17 @@ DEFAULT_METHOD = 'GET'
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup REST binary sensors."""
|
||||
"""Setup the REST binary sensor."""
|
||||
resource = config.get('resource', None)
|
||||
method = config.get('method', DEFAULT_METHOD)
|
||||
payload = config.get('payload', None)
|
||||
verify_ssl = config.get('verify_ssl', True)
|
||||
|
||||
sensor_class = config.get('sensor_class')
|
||||
if sensor_class not in SENSOR_CLASSES:
|
||||
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
|
||||
sensor_class = None
|
||||
|
||||
rest = RestData(method, resource, payload, verify_ssl)
|
||||
rest.update()
|
||||
|
||||
@@ -33,28 +39,37 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
return False
|
||||
|
||||
add_devices([RestBinarySensor(
|
||||
hass, rest, config.get('name', DEFAULT_NAME),
|
||||
hass,
|
||||
rest,
|
||||
config.get('name', DEFAULT_NAME),
|
||||
sensor_class,
|
||||
config.get(CONF_VALUE_TEMPLATE))])
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
class RestBinarySensor(BinarySensorDevice):
|
||||
"""A REST binary sensor."""
|
||||
"""Representation of a REST binary sensor."""
|
||||
|
||||
def __init__(self, hass, rest, name, value_template):
|
||||
def __init__(self, hass, rest, name, sensor_class, value_template):
|
||||
"""Initialize a REST binary sensor."""
|
||||
self._hass = hass
|
||||
self.rest = rest
|
||||
self._name = name
|
||||
self._sensor_class = sensor_class
|
||||
self._state = False
|
||||
self._value_template = value_template
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the binary sensor."""
|
||||
"""Return the name of the binary sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def sensor_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return self._sensor_class
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
|
||||
@@ -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
|
||||
https://home-assistant.io/components/binary_sensor.rpi_gpio/
|
||||
@@ -20,8 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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)
|
||||
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
|
||||
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
|
||||
class RPiGPIOBinarySensor(BinarySensorDevice):
|
||||
"""Represents a binary sensor that uses Raspberry Pi GPIO."""
|
||||
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
|
||||
# pylint: disable=no-member
|
||||
"""Represent a binary sensor that uses Raspberry Pi GPIO."""
|
||||
|
||||
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._port = port
|
||||
self._pull_mode = pull_mode
|
||||
@@ -50,9 +50,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
|
||||
self._state = rpi_gpio.read_input(self._port)
|
||||
|
||||
def read_gpio(port):
|
||||
"""Reads state from GPIO."""
|
||||
"""Read state from GPIO."""
|
||||
self._state = rpi_gpio.read_input(self._port)
|
||||
self.update_ha_state()
|
||||
|
||||
rpi_gpio.edge_detect(self._port, read_gpio, self._bouncetime)
|
||||
|
||||
@property
|
||||
@@ -62,10 +63,10 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the sensor."""
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Returns the state of the entity."""
|
||||
"""Return the state of the entity."""
|
||||
return self._state != self._invert_logic
|
||||
|
||||
@@ -23,6 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
class BinarySensor(BinarySensorDevice, Sensor):
|
||||
"""A binary sensor which is on when its state == CONF_VALUE_ON."""
|
||||
|
||||
required = (CONF_VALUE_ON,)
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"""
|
||||
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
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
||||
DOMAIN,
|
||||
ENTITY_ID_FORMAT,
|
||||
SENSOR_CLASSES)
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE
|
||||
from homeassistant.core import EVENT_STATE_CHANGED
|
||||
@@ -15,14 +16,12 @@ from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.util import slugify
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
CONF_SENSORS = 'sensors'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup template binary sensors."""
|
||||
|
||||
sensors = []
|
||||
if config.get(CONF_SENSORS) is None:
|
||||
_LOGGER.error('Missing configuration data for binary_sensor platform')
|
||||
@@ -70,47 +69,54 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
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
|
||||
def __init__(self, hass, device, friendly_name, sensor_class,
|
||||
value_template):
|
||||
self._hass = hass
|
||||
self._device = device
|
||||
"""Initialize the Template binary sensor."""
|
||||
self.hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
|
||||
hass=hass)
|
||||
self._name = friendly_name
|
||||
self._sensor_class = sensor_class
|
||||
self._template = value_template
|
||||
self._state = None
|
||||
|
||||
self.entity_id = generate_entity_id(
|
||||
ENTITY_ID_FORMAT, device,
|
||||
hass=hass)
|
||||
self.update()
|
||||
|
||||
_LOGGER.info('Started template sensor %s', device)
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
||||
def template_bsensor_event_listener(event):
|
||||
"""Called when the target device changes state."""
|
||||
self.update_ha_state(True)
|
||||
|
||||
def _event_listener(self, event):
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def sensor_class(self):
|
||||
return self._sensor_class
|
||||
hass.bus.listen(EVENT_STATE_CHANGED,
|
||||
template_bsensor_event_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if sensor is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def sensor_class(self):
|
||||
"""Return the sensor class of the sensor."""
|
||||
return self._sensor_class
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data and update the state."""
|
||||
try:
|
||||
value = template.render(self._hass, self._template)
|
||||
self._state = template.render(self.hass,
|
||||
self._template).lower() == 'true'
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
@@ -118,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
_LOGGER.warning(ex)
|
||||
return
|
||||
_LOGGER.error(ex)
|
||||
value = 'false'
|
||||
self._state = value.lower() == 'true'
|
||||
self._state = False
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Support for Vera binary sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.vera/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED)
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice)
|
||||
from homeassistant.components.vera import (
|
||||
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
|
||||
|
||||
DEPENDENCIES = ['vera']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Perform the setup for Vera controller devices."""
|
||||
add_devices_callback(
|
||||
VeraBinarySensor(device, VERA_CONTROLLER)
|
||||
for device in VERA_DEVICES['binary_sensor'])
|
||||
|
||||
|
||||
class VeraBinarySensor(VeraDevice, BinarySensorDevice):
|
||||
"""Representation of a Vera Binary Sensor."""
|
||||
|
||||
def __init__(self, vera_device, controller):
|
||||
"""Initialize the binary_sensor."""
|
||||
self._state = False
|
||||
VeraDevice.__init__(self, vera_device, controller)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attr = {}
|
||||
if self.vera_device.has_battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
||||
|
||||
if self.vera_device.is_armable:
|
||||
armed = self.vera_device.is_armed
|
||||
attr[ATTR_ARMED] = 'True' if armed else 'False'
|
||||
|
||||
if self.vera_device.is_trippable:
|
||||
last_tripped = self.vera_device.last_trip
|
||||
if last_tripped is not None:
|
||||
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
|
||||
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
|
||||
utc_time)
|
||||
else:
|
||||
attr[ATTR_LAST_TRIP_TIME] = None
|
||||
tripped = self.vera_device.is_tripped
|
||||
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
|
||||
|
||||
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||
return attr
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if sensor is on."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data and update the state."""
|
||||
self._state = self.vera_device.is_tripped
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
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)
|
||||
if not hasattr(self, 'hass'):
|
||||
self.update()
|
||||
return
|
||||
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)
|
||||
@@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['python-wink==0.6.2']
|
||||
REQUIREMENTS = ['python-wink==0.6.4']
|
||||
|
||||
# These are the available sensors mapped to binary_sensor class
|
||||
SENSOR_TYPES = {
|
||||
@@ -22,7 +22,7 @@ SENSOR_TYPES = {
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Sets up the Wink platform."""
|
||||
"""Setup the Wink platform."""
|
||||
import pywink
|
||||
|
||||
if discovery_info is None:
|
||||
@@ -42,16 +42,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
||||
"""Represents a Wink sensor."""
|
||||
"""Representation of a Wink sensor."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the Wink binary sensor."""
|
||||
self.wink = wink
|
||||
self._unit_of_measurement = self.wink.UNIT
|
||||
self.capability = self.wink.capability()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
if self.capability == "loudness":
|
||||
return self.wink.loudness_boolean()
|
||||
elif self.capability == "vibration":
|
||||
@@ -68,14 +69,19 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
||||
|
||||
@property
|
||||
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())
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the sensor if any. """
|
||||
"""Return the name of the sensor if any."""
|
||||
return self.wink.name()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def update(self):
|
||||
""" Update state of the sensor. """
|
||||
"""Update state of the sensor."""
|
||||
self.wink.update_state()
|
||||
|
||||
@@ -19,8 +19,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
|
||||
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):
|
||||
"""
|
||||
Use multiple inheritance to turn a ZigBeeDigitalIn into a
|
||||
BinarySensorDevice.
|
||||
"""
|
||||
"""Use ZigBeeDigitalIn as binary sensor."""
|
||||
|
||||
pass
|
||||
|
||||
@@ -23,17 +23,19 @@ DEPENDENCIES = []
|
||||
PHILIO = 0x013c
|
||||
PHILIO_SLIM_SENSOR = 0x0002
|
||||
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'
|
||||
|
||||
DEVICE_MAPPINGS = {
|
||||
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):
|
||||
"""Setup the Z-Wave platform for sensors."""
|
||||
|
||||
if discovery_info is None or NETWORK is None:
|
||||
return
|
||||
|
||||
@@ -63,9 +65,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
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):
|
||||
"""Initialize the sensor."""
|
||||
self._sensor_type = sensor_class
|
||||
# pylint: disable=import-error
|
||||
from openzwave.network import ZWaveNetwork
|
||||
@@ -98,12 +101,10 @@ class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity):
|
||||
|
||||
|
||||
class ZWaveTriggerSensor(ZWaveBinarySensor):
|
||||
"""
|
||||
Represents a stateless sensor which triggers events just 'On'
|
||||
within Z-Wave.
|
||||
"""
|
||||
"""Representation of a stateless sensor within Z-Wave."""
|
||||
|
||||
def __init__(self, sensor_value, sensor_class, hass, re_arm_sec=60):
|
||||
"""Initialize the sensor."""
|
||||
super(ZWaveTriggerSensor, self).__init__(sensor_value, sensor_class)
|
||||
self._hass = hass
|
||||
self.re_arm_sec = re_arm_sec
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.bloomsky
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for BloomSky weather station.
|
||||
|
||||
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
|
||||
def setup(hass, config):
|
||||
""" Setup BloomSky component. """
|
||||
"""Setup BloomSky component."""
|
||||
if not validate_config(
|
||||
config,
|
||||
{DOMAIN: [CONF_API_KEY]},
|
||||
@@ -57,13 +55,13 @@ def setup(hass, config):
|
||||
|
||||
|
||||
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_URL = "https://api.bloomsky.com/api/skydata"
|
||||
|
||||
def __init__(self, api_key):
|
||||
"""Initialize the BookSky."""
|
||||
self._api_key = api_key
|
||||
self.devices = {}
|
||||
_LOGGER.debug("Initial bloomsky device load...")
|
||||
@@ -71,10 +69,7 @@ class BloomSky(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def refresh_devices(self):
|
||||
"""
|
||||
Uses the API to retreive a list of devices associated with an
|
||||
account along with all the sensors on the device.
|
||||
"""
|
||||
"""Use the API to retreive a list of devices."""
|
||||
_LOGGER.debug("Fetching bloomsky update")
|
||||
response = requests.get(self.API_URL,
|
||||
headers={"Authorization": self._api_key},
|
||||
@@ -84,7 +79,7 @@ class BloomSky(object):
|
||||
elif response.status_code != 200:
|
||||
_LOGGER.error("Invalid HTTP response: %s", response.status_code)
|
||||
return
|
||||
# create dictionary keyed off of the device unique id
|
||||
# Create dictionary keyed off of the device unique id
|
||||
self.devices.update({
|
||||
device["DeviceID"]: device for device in response.json()
|
||||
})
|
||||
|
||||
@@ -10,9 +10,7 @@ SERVICE_BROWSE_URL = "browse_url"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""
|
||||
Listen for browse_url events and open the url in the default web browser.
|
||||
"""
|
||||
"""Listen for browse_url events."""
|
||||
import webbrowser
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_BROWSE_URL,
|
||||
|
||||
@@ -42,7 +42,7 @@ MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n'
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def setup(hass, config):
|
||||
"""Initialize camera component."""
|
||||
"""Setup the camera component."""
|
||||
component = EntityComponent(
|
||||
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL,
|
||||
DISCOVERY_PLATFORMS)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.camera.bloomsky
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for a camera of a BloomSky weather station.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
@@ -18,17 +16,17 @@ DEPENDENCIES = ["bloomsky"]
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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')
|
||||
for device in bloomsky.BLOOMSKY.devices.values():
|
||||
add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
|
||||
|
||||
|
||||
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):
|
||||
""" set up for access to the BloomSky camera images """
|
||||
"""Setup for access to the BloomSky camera images."""
|
||||
super(BloomSkyCamera, self).__init__()
|
||||
self._name = device["DeviceName"]
|
||||
self._id = device["DeviceID"]
|
||||
@@ -37,16 +35,16 @@ class BloomSkyCamera(Camera):
|
||||
self._last_url = ""
|
||||
# _last_image will store images as they are downloaded so that the
|
||||
# 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._logger = logging.getLogger(__name__)
|
||||
|
||||
def camera_image(self):
|
||||
""" Update the camera's image if it has changed. """
|
||||
"""Update the camera's image if it has changed."""
|
||||
try:
|
||||
self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"]
|
||||
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:
|
||||
response = requests.get(self._url, timeout=10)
|
||||
self._last_url = self._url
|
||||
@@ -59,5 +57,5 @@ class BloomSkyCamera(Camera):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" The name of this BloomSky device. """
|
||||
"""Return the name of this BloomSky device."""
|
||||
return self._name
|
||||
|
||||
@@ -21,6 +21,7 @@ class DemoCamera(Camera):
|
||||
"""A Demo camera."""
|
||||
|
||||
def __init__(self, name):
|
||||
"""Initialize demo camera component."""
|
||||
super().__init__()
|
||||
self._name = name
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.camera.foscam
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
This component provides basic support for Foscam IP cameras.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
@@ -18,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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},
|
||||
{DOMAIN: ['username', 'password', 'ip']}, _LOGGER):
|
||||
return None
|
||||
@@ -28,9 +26,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class FoscamCamera(Camera):
|
||||
""" An implementation of a Foscam IP camera. """
|
||||
"""An implementation of a Foscam IP camera."""
|
||||
|
||||
def __init__(self, device_info):
|
||||
"""Initialize a Foscam camera."""
|
||||
super(FoscamCamera, self).__init__()
|
||||
|
||||
ip_address = device_info.get('ip')
|
||||
@@ -48,8 +47,7 @@ class FoscamCamera(Camera):
|
||||
self._name, self._snap_picture_url)
|
||||
|
||||
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
|
||||
response = requests.get(self._snap_picture_url)
|
||||
|
||||
@@ -57,5 +55,5 @@ class FoscamCamera(Camera):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Return the name of this device. """
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.camera.generic
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for IP Cameras.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
@@ -19,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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']},
|
||||
_LOGGER):
|
||||
return None
|
||||
@@ -29,11 +27,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class GenericCamera(Camera):
|
||||
"""
|
||||
A generic implementation of an IP camera that is reachable over a URL.
|
||||
"""
|
||||
"""A generic implementation of an IP camera."""
|
||||
|
||||
def __init__(self, device_info):
|
||||
"""Initialize a generic camera."""
|
||||
super().__init__()
|
||||
self._name = device_info.get('name', 'Generic Camera')
|
||||
self._username = device_info.get('username')
|
||||
@@ -41,7 +38,7 @@ class GenericCamera(Camera):
|
||||
self._still_image_url = device_info['still_image_url']
|
||||
|
||||
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:
|
||||
try:
|
||||
response = requests.get(
|
||||
@@ -61,5 +58,5 @@ class GenericCamera(Camera):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Return the name of this device. """
|
||||
"""Return the name of this device."""
|
||||
return self._name
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.camera.mjpeg
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for IP Cameras.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
@@ -23,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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']},
|
||||
_LOGGER):
|
||||
return None
|
||||
@@ -33,11 +31,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class MjpegCamera(Camera):
|
||||
"""
|
||||
A generic implementation of an IP camera that is reachable over a URL.
|
||||
"""
|
||||
"""An implementation of an IP camera that is reachable over a URL."""
|
||||
|
||||
def __init__(self, device_info):
|
||||
"""Initialize a MJPEG camera."""
|
||||
super().__init__()
|
||||
self._name = device_info.get('name', 'Mjpeg Camera')
|
||||
self._username = device_info.get('username')
|
||||
@@ -45,7 +42,7 @@ class MjpegCamera(Camera):
|
||||
self._mjpeg_url = device_info['mjpeg_url']
|
||||
|
||||
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:
|
||||
return requests.get(self._mjpeg_url,
|
||||
auth=HTTPBasicAuth(self._username,
|
||||
@@ -56,10 +53,9 @@ class MjpegCamera(Camera):
|
||||
stream=True)
|
||||
|
||||
def camera_image(self):
|
||||
""" Return a still image response from the camera. """
|
||||
|
||||
"""Return a still image response from the camera."""
|
||||
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''
|
||||
for chunk in response.iter_content(1024):
|
||||
data += chunk
|
||||
@@ -73,7 +69,7 @@ class MjpegCamera(Camera):
|
||||
return process_response(response)
|
||||
|
||||
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()
|
||||
content_type = response.headers[CONTENT_TYPE_HEADER]
|
||||
|
||||
@@ -88,5 +84,5 @@ class MjpegCamera(Camera):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Return the name of this device. """
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.camera.uvc
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for Ubiquiti's UVC cameras.
|
||||
|
||||
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):
|
||||
""" Discover cameras on a Unifi NVR. """
|
||||
"""Discover cameras on a Unifi NVR."""
|
||||
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']},
|
||||
_LOGGER):
|
||||
return None
|
||||
@@ -60,9 +58,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class UnifiVideoCamera(Camera):
|
||||
""" A Ubiquiti Unifi Video Camera. """
|
||||
"""A Ubiquiti Unifi Video Camera."""
|
||||
|
||||
def __init__(self, nvr, uuid, name):
|
||||
"""Initialize an Unifi camera."""
|
||||
super(UnifiVideoCamera, self).__init__()
|
||||
self._nvr = nvr
|
||||
self._uuid = uuid
|
||||
@@ -73,23 +72,28 @@ class UnifiVideoCamera(Camera):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_recording(self):
|
||||
"""Return true if the camera is recording."""
|
||||
caminfo = self._nvr.get_camera(self._uuid)
|
||||
return caminfo['recordingSettings']['fullTimeRecordEnabled']
|
||||
|
||||
@property
|
||||
def brand(self):
|
||||
"""Return the brand of this camera."""
|
||||
return 'Ubiquiti'
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
"""Return the model of this camera."""
|
||||
caminfo = self._nvr.get_camera(self._uuid)
|
||||
return caminfo['model']
|
||||
|
||||
def _login(self):
|
||||
"""Login to the camera."""
|
||||
from uvcclient import camera as uvc_camera
|
||||
from uvcclient import store as uvc_store
|
||||
|
||||
@@ -131,6 +135,7 @@ class UnifiVideoCamera(Camera):
|
||||
return True
|
||||
|
||||
def camera_image(self):
|
||||
"""Return the image of this camera."""
|
||||
from uvcclient import camera as uvc_camera
|
||||
if not self._camera:
|
||||
if not self._login():
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.configurator
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A component to allow pieces of code to request configuration from the user.
|
||||
Support to allow pieces of code to request configuration from the user.
|
||||
|
||||
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.
|
||||
@@ -38,9 +35,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
def request_config(
|
||||
hass, name, callback, description=None, description_image=None,
|
||||
submit_caption=None, fields=None):
|
||||
""" Create a new request for config.
|
||||
Will return an ID to be used for sequent calls. """
|
||||
"""Create a new request for configuration.
|
||||
|
||||
Will return an ID to be used for sequent calls.
|
||||
"""
|
||||
instance = _get_instance(hass)
|
||||
|
||||
request_id = instance.request_config(
|
||||
@@ -53,7 +51,7 @@ def request_config(
|
||||
|
||||
|
||||
def notify_errors(request_id, error):
|
||||
""" Add errors to a config request. """
|
||||
"""Add errors to a config request."""
|
||||
try:
|
||||
_REQUESTS[request_id].notify_errors(request_id, error)
|
||||
except KeyError:
|
||||
@@ -62,7 +60,7 @@ def notify_errors(request_id, error):
|
||||
|
||||
|
||||
def request_done(request_id):
|
||||
""" Mark a config request as done. """
|
||||
"""Mark a configuration request as done."""
|
||||
try:
|
||||
_REQUESTS.pop(request_id).request_done(request_id)
|
||||
except KeyError:
|
||||
@@ -71,12 +69,12 @@ def request_done(request_id):
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Set up Configurator. """
|
||||
"""Setup the configurator component."""
|
||||
return True
|
||||
|
||||
|
||||
def _get_instance(hass):
|
||||
""" Get an instance per hass object. """
|
||||
"""Get an instance per hass object."""
|
||||
try:
|
||||
return _INSTANCES[hass]
|
||||
except KeyError:
|
||||
@@ -89,11 +87,10 @@ def _get_instance(hass):
|
||||
|
||||
|
||||
class Configurator(object):
|
||||
"""
|
||||
Class to keep track of current configuration requests.
|
||||
"""
|
||||
"""The class to keep track of current configuration requests."""
|
||||
|
||||
def __init__(self, hass):
|
||||
"""Initialize the configurator."""
|
||||
self.hass = hass
|
||||
self._cur_id = 0
|
||||
self._requests = {}
|
||||
@@ -104,8 +101,7 @@ class Configurator(object):
|
||||
def request_config(
|
||||
self, name, callback,
|
||||
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)
|
||||
|
||||
if fields is None:
|
||||
@@ -133,7 +129,7 @@ class Configurator(object):
|
||||
return request_id
|
||||
|
||||
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):
|
||||
return
|
||||
|
||||
@@ -147,7 +143,7 @@ class Configurator(object):
|
||||
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
|
||||
|
||||
def request_done(self, request_id):
|
||||
""" Remove the config request. """
|
||||
"""Remove the configuration request."""
|
||||
if not self._validate_request_id(request_id):
|
||||
return
|
||||
|
||||
@@ -160,13 +156,13 @@ class Configurator(object):
|
||||
self.hass.states.set(entity_id, STATE_CONFIGURED)
|
||||
|
||||
def deferred_remove(event):
|
||||
""" Remove the request state. """
|
||||
"""Remove the request state."""
|
||||
self.hass.states.remove(entity_id)
|
||||
|
||||
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
|
||||
|
||||
def handle_service_call(self, call):
|
||||
""" Handle a configure service call. """
|
||||
"""Handle a configure service call."""
|
||||
request_id = call.data.get(ATTR_CONFIGURE_ID)
|
||||
|
||||
if not self._validate_request_id(request_id):
|
||||
@@ -180,10 +176,10 @@ class Configurator(object):
|
||||
callback(call.data.get(ATTR_FIELDS, {}))
|
||||
|
||||
def _generate_unique_id(self):
|
||||
""" Generates a unique configurator id. """
|
||||
"""Generate a unique configurator ID."""
|
||||
self._cur_id += 1
|
||||
return "{}-{}".format(id(self), self._cur_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
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.conversation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides functionality to have conversations with Home Assistant.
|
||||
Support for functionality to have conversations with Home Assistant.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/conversation/
|
||||
@@ -25,19 +23,18 @@ REQUIREMENTS = ['fuzzywuzzy==0.8.0']
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Registers the process service. """
|
||||
"""Register the process service."""
|
||||
from fuzzywuzzy import process as fuzzyExtract
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def process(service):
|
||||
""" Parses text into commands for Home Assistant. """
|
||||
"""Parse text into commands."""
|
||||
if ATTR_TEXT not in service.data:
|
||||
logger.error("Received process service call without a text")
|
||||
return
|
||||
|
||||
text = service.data[ATTR_TEXT].lower()
|
||||
|
||||
match = REGEX_TURN_COMMAND.match(text)
|
||||
|
||||
if not match:
|
||||
@@ -45,11 +42,8 @@ def setup(hass, config):
|
||||
return
|
||||
|
||||
name, command = match.groups()
|
||||
|
||||
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]
|
||||
|
||||
if not entity_ids:
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_sun_light_trigger
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides functionality to turn on lights based on the state of the sun and
|
||||
devices.
|
||||
Provides functionality to turn on lights based on the states.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_sun_light_trigger/
|
||||
@@ -12,9 +9,9 @@ from datetime import timedelta
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
|
||||
from homeassistant.helpers.event import track_point_in_time, track_state_change
|
||||
|
||||
from . import device_tracker, group, light, sun
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
from homeassistant.helpers.event_decorators import track_state_change
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
DOMAIN = "device_sun_light_trigger"
|
||||
DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun']
|
||||
@@ -29,28 +26,26 @@ CONF_LIGHT_GROUP = 'light_group'
|
||||
CONF_DEVICE_GROUP = 'device_group'
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
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]
|
||||
|
||||
light_group = config[DOMAIN].get(CONF_LIGHT_GROUP,
|
||||
light.ENTITY_ID_ALL_LIGHTS)
|
||||
|
||||
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE, LIGHT_PROFILE)
|
||||
|
||||
device_group = config[DOMAIN].get(CONF_DEVICE_GROUP,
|
||||
device_tracker.ENTITY_ID_ALL_DEVICES)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
device_entity_ids = group.get_entity_ids(hass, device_group,
|
||||
device_tracker.DOMAIN)
|
||||
|
||||
if not device_entity_ids:
|
||||
logger.error("No devices found to track")
|
||||
|
||||
return False
|
||||
|
||||
# Get the light IDs from the specified group
|
||||
@@ -58,116 +53,105 @@ def setup(hass, config):
|
||||
|
||||
if not light_ids:
|
||||
logger.error("No lights found to turn on ")
|
||||
|
||||
return False
|
||||
|
||||
def calc_time_for_light_when_sunset():
|
||||
""" Calculates the time when to start fading lights in when sun sets.
|
||||
Returns None if no next_setting data available. """
|
||||
"""Calculate the time when to start fading lights in when sun sets.
|
||||
|
||||
Returns None if no next_setting data available.
|
||||
"""
|
||||
next_setting = sun.next_setting(hass)
|
||||
|
||||
if next_setting:
|
||||
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
|
||||
else:
|
||||
if not next_setting:
|
||||
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):
|
||||
"""Helper function to turn on lights.
|
||||
|
||||
def turn_light_on_before_sunset(light_id):
|
||||
""" Helper function to turn on lights slowly if there
|
||||
are devices home and the light is not on yet. """
|
||||
if device_tracker.is_on(hass) and not light.is_on(hass, light_id):
|
||||
|
||||
light.turn_on(hass, light_id,
|
||||
transition=LIGHT_TRANSITION_TIME.seconds,
|
||||
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))
|
||||
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,
|
||||
transition=LIGHT_TRANSITION_TIME.seconds,
|
||||
profile=light_profile)
|
||||
|
||||
# Track every time sun rises so we can schedule a time-based
|
||||
# pre-sun set event
|
||||
track_state_change(hass, sun.ENTITY_ID, schedule_light_on_sun_rise,
|
||||
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
|
||||
@track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_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
|
||||
# schedule the time-based pre-sun set event
|
||||
We will schedule to have each light start after one another
|
||||
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):
|
||||
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):
|
||||
""" Function to handle tracked device state changes. """
|
||||
@track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME)
|
||||
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)
|
||||
|
||||
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
|
||||
now = dt_util.now()
|
||||
start_point = calc_time_for_light_when_sunset()
|
||||
|
||||
# These variables are needed for the elif check
|
||||
now = dt_util.now()
|
||||
start_point = calc_time_for_light_when_sunset()
|
||||
# Do we need lights?
|
||||
if light_needed:
|
||||
logger.info("Home coming event for %s. Turning lights on", entity)
|
||||
light.turn_on(hass, light_ids, profile=light_profile)
|
||||
|
||||
# Do we need lights?
|
||||
if light_needed:
|
||||
# Are we in the time span were we would turn on the lights
|
||||
# if someone would be home?
|
||||
# Check this by seeing if current time is later then the point
|
||||
# in time when we would start putting the lights on.
|
||||
elif (start_point and
|
||||
start_point < now < sun.next_setting(hass)):
|
||||
|
||||
logger.info(
|
||||
"Home coming event for %s. Turning lights on", entity)
|
||||
# Check for every light if it would be on if someone was home
|
||||
# when the fading in started and turn it on if so
|
||||
for index, light_id in enumerate(light_ids):
|
||||
if now > start_point + index * LIGHT_TRANSITION_TIME:
|
||||
light.turn_on(hass, light_id)
|
||||
|
||||
light.turn_on(hass, light_ids, profile=light_profile)
|
||||
else:
|
||||
# If this light didn't happen to be turned on yet so
|
||||
# will all the following then, break.
|
||||
break
|
||||
|
||||
# Are we in the time span were we would turn on the lights
|
||||
# if someone would be home?
|
||||
# Check this by seeing if current time is later then the point
|
||||
# in time when we would start putting the lights on.
|
||||
elif (start_point and
|
||||
start_point < now < sun.next_setting(hass)):
|
||||
|
||||
# Check for every light if it would be on if someone was home
|
||||
# when the fading in started and turn it on if so
|
||||
for index, light_id in enumerate(light_ids):
|
||||
|
||||
if now > start_point + index * LIGHT_TRANSITION_TIME:
|
||||
light.turn_on(hass, light_id)
|
||||
|
||||
else:
|
||||
# If this light didn't happen to be turned on yet so
|
||||
# will all the following then, break.
|
||||
break
|
||||
|
||||
# Did all devices leave the house?
|
||||
elif (entity == device_group and
|
||||
new_state.state == STATE_NOT_HOME and lights_are_on and
|
||||
not disable_turn_off):
|
||||
if not disable_turn_off:
|
||||
@track_state_change(device_group, STATE_HOME, STATE_NOT_HOME)
|
||||
def turn_off_lights_when_all_leave(hass, entity, old_state, new_state):
|
||||
"""Handle device group state change."""
|
||||
# pylint: disable=unused-variable
|
||||
if not group.is_on(hass, light_group):
|
||||
return
|
||||
|
||||
logger.info(
|
||||
"Everyone has left but there are lights on. Turning them off")
|
||||
|
||||
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
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides functionality to keep track of devices.
|
||||
Provide functionality to keep track of devices.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker/
|
||||
"""
|
||||
# pylint: disable=too-many-instance-attributes, too-many-arguments
|
||||
# pylint: disable=too-many-locals
|
||||
import csv
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import os
|
||||
@@ -36,7 +33,6 @@ ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
CSV_DEVICES = "known_devices.csv"
|
||||
YAML_DEVICES = 'known_devices.yaml'
|
||||
|
||||
CONF_TRACK_NEW = "track_new_devices"
|
||||
@@ -72,7 +68,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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
|
||||
|
||||
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,
|
||||
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
|
||||
((ATTR_MAC, mac),
|
||||
(ATTR_DEV_ID, dev_id),
|
||||
(ATTR_HOST_NAME, host_name),
|
||||
(ATTR_LOCATION_NAME, location_name),
|
||||
(ATTR_GPS, gps)) if value is not None}
|
||||
(ATTR_GPS, gps),
|
||||
(ATTR_GPS_ACCURACY, gps_accuracy),
|
||||
(ATTR_BATTERY, battery)) if value is not None}
|
||||
hass.services.call(DOMAIN, SERVICE_SEE, data)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup device tracker """
|
||||
"""Setup device tracker."""
|
||||
yaml_path = hass.config.path(YAML_DEVICES)
|
||||
csv_path = hass.config.path(CSV_DEVICES)
|
||||
if os.path.isfile(csv_path) and not os.path.isfile(yaml_path) and \
|
||||
convert_csv_config(csv_path, yaml_path):
|
||||
os.remove(csv_path)
|
||||
|
||||
conf = config.get(DOMAIN, {})
|
||||
if isinstance(conf, list):
|
||||
@@ -114,7 +108,7 @@ def setup(hass, config):
|
||||
devices)
|
||||
|
||||
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)
|
||||
if platform is None:
|
||||
return
|
||||
@@ -140,21 +134,21 @@ def setup(hass, config):
|
||||
setup_platform(p_type, p_config)
|
||||
|
||||
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)
|
||||
|
||||
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(),
|
||||
device_tracker_discovered)
|
||||
|
||||
def update_stale(now):
|
||||
""" Clean up stale devices. """
|
||||
"""Clean up stale devices."""
|
||||
tracker.update_stale(now)
|
||||
track_utc_time_change(hass, update_stale, second=range(0, 60, 5))
|
||||
|
||||
tracker.setup_group()
|
||||
|
||||
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
|
||||
(ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
|
||||
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)}
|
||||
@@ -169,8 +163,10 @@ def setup(hass, config):
|
||||
|
||||
|
||||
class DeviceTracker(object):
|
||||
""" Track devices """
|
||||
"""Representation of a device tracker."""
|
||||
|
||||
def __init__(self, hass, consider_home, track_new, home_range, devices):
|
||||
"""Initialize a device tracker."""
|
||||
self.hass = hass
|
||||
self.devices = {dev.dev_id: dev for dev in devices}
|
||||
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
|
||||
@@ -187,7 +183,7 @@ class DeviceTracker(object):
|
||||
|
||||
def see(self, mac=None, dev_id=None, host_name=None, location_name=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:
|
||||
if mac is None and dev_id is None:
|
||||
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)
|
||||
|
||||
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()
|
||||
if dev.track)
|
||||
self.group = group.Group(
|
||||
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
|
||||
|
||||
def update_stale(self, now):
|
||||
""" Update stale devices. """
|
||||
"""Update stale devices."""
|
||||
with self.lock:
|
||||
for device in self.devices.values():
|
||||
if (device.track and device.last_update_home and
|
||||
@@ -242,7 +238,7 @@ class DeviceTracker(object):
|
||||
|
||||
|
||||
class Device(Entity):
|
||||
""" Tracked device. """
|
||||
"""Represent a tracked device."""
|
||||
|
||||
host_name = None
|
||||
location_name = None
|
||||
@@ -251,12 +247,13 @@ class Device(Entity):
|
||||
last_seen = 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
|
||||
_state = STATE_NOT_HOME
|
||||
|
||||
def __init__(self, hass, consider_home, home_range, track, dev_id, mac,
|
||||
name=None, picture=None, away_hide=False):
|
||||
"""Initialize a device."""
|
||||
self.hass = hass
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
|
||||
|
||||
@@ -282,29 +279,29 @@ class Device(Entity):
|
||||
|
||||
@property
|
||||
def gps_home(self):
|
||||
""" Return if device is within range of home. """
|
||||
"""Return if device is within range of home."""
|
||||
distance = max(
|
||||
0, self.hass.config.distance(*self.gps) - self.gps_accuracy)
|
||||
return self.gps is not None and distance <= self.home_range
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" State of the device. """
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def entity_picture(self):
|
||||
"""Picture of the device."""
|
||||
"""Return the picture of the device."""
|
||||
return self.config_picture
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Device state attributes. """
|
||||
"""Return the device state attributes."""
|
||||
attr = {}
|
||||
|
||||
if self.gps:
|
||||
@@ -319,12 +316,12 @@ class Device(Entity):
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
""" If device should be hidden. """
|
||||
"""If device should be hidden."""
|
||||
return self.away_hide and self.state != STATE_HOME
|
||||
|
||||
def seen(self, host_name=None, location_name=None, gps=None,
|
||||
gps_accuracy=0, battery=None):
|
||||
""" Mark the device as seen. """
|
||||
"""Mark the device as seen."""
|
||||
self.last_seen = dt_util.utcnow()
|
||||
self.host_name = host_name
|
||||
self.location_name = location_name
|
||||
@@ -342,12 +339,12 @@ class Device(Entity):
|
||||
self.update()
|
||||
|
||||
def stale(self, now=None):
|
||||
""" Return if device state is stale. """
|
||||
"""Return if device state is stale."""
|
||||
return self.last_seen and \
|
||||
(now or dt_util.utcnow()) - self.last_seen > self.consider_home
|
||||
|
||||
def update(self):
|
||||
""" Update state of entity. """
|
||||
"""Update state of entity."""
|
||||
if not self.last_seen:
|
||||
return
|
||||
elif self.location_name:
|
||||
@@ -370,23 +367,8 @@ class Device(Entity):
|
||||
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):
|
||||
""" Load devices from YAML config file. """
|
||||
"""Load devices from YAML configuration file."""
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
return [
|
||||
@@ -398,7 +380,7 @@ def load_config(path, hass, consider_home, home_range):
|
||||
|
||||
|
||||
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,
|
||||
DEFAULT_SCAN_INTERVAL)
|
||||
|
||||
@@ -406,7 +388,7 @@ def setup_scanner_platform(hass, config, scanner, see_device):
|
||||
seen = set()
|
||||
|
||||
def device_tracker_scan(now):
|
||||
""" Called when interval matches. """
|
||||
"""Called when interval matches."""
|
||||
for mac in scanner.scan_devices():
|
||||
if mac in seen:
|
||||
host_name = None
|
||||
@@ -422,7 +404,7 @@ def setup_scanner_platform(hass, config, scanner, see_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:
|
||||
out.write('\n')
|
||||
out.write('{}:\n'.format(device.dev_id))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.actiontec
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning an Actiontec MI424WR
|
||||
(Verizon FIOS) router for device presence.
|
||||
Support for Actiontec MI424WR (Verizon FIOS) routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -34,7 +31,7 @@ _LEASES_REGEX = re.compile(
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@@ -46,12 +43,10 @@ Device = namedtuple("Device", ["mac", "ip", "last_update"])
|
||||
|
||||
|
||||
class ActiontecDeviceScanner(object):
|
||||
"""
|
||||
This class queries a an actiontec router for connected devices.
|
||||
Adapted from DD-WRT scanner.
|
||||
"""
|
||||
"""This class queries a an actiontec router for connected devices."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = config[CONF_USERNAME]
|
||||
self.password = config[CONF_PASSWORD]
|
||||
@@ -62,15 +57,12 @@ class ActiontecDeviceScanner(object):
|
||||
_LOGGER.info("actiontec scanner initialized")
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
return [client.mac for client in self.last_results]
|
||||
|
||||
def get_device_name(self, device):
|
||||
""" Returns the name of the given device or None if we don't know. """
|
||||
"""Return the name of the given device or None if we don't know."""
|
||||
if not self.last_results:
|
||||
return None
|
||||
for client in self.last_results:
|
||||
@@ -80,9 +72,9 @@ class ActiontecDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the Actiontec MI424WR router is up
|
||||
to date. Returns boolean if scanning successful.
|
||||
"""Ensure the information from the router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
_LOGGER.info("Scanning")
|
||||
if not self.success_init:
|
||||
@@ -100,7 +92,7 @@ class ActiontecDeviceScanner(object):
|
||||
return True
|
||||
|
||||
def get_actiontec_data(self):
|
||||
""" Retrieve data from Actiontec MI424WR and return parsed result. """
|
||||
"""Retrieve data from Actiontec MI424WR and return parsed result."""
|
||||
try:
|
||||
telnet = telnetlib.Telnet(self.host)
|
||||
telnet.read_until(b'Username: ')
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.aruba
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a Aruba Access Point for device
|
||||
presence.
|
||||
Support for Aruba Access Points.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.aruba/
|
||||
@@ -31,7 +28,7 @@ _DEVICES_REGEX = re.compile(
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@@ -43,9 +40,10 @@ def get_scanner(hass, config):
|
||||
|
||||
|
||||
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):
|
||||
"""Initialize the scanner."""
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = config[CONF_USERNAME]
|
||||
self.password = config[CONF_PASSWORD]
|
||||
@@ -54,20 +52,17 @@ class ArubaDeviceScanner(object):
|
||||
|
||||
self.last_results = {}
|
||||
|
||||
# Test the router is accessible
|
||||
# Test the router is accessible.
|
||||
data = self.get_aruba_data()
|
||||
self.success_init = data is not None
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device IDs.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
return [client['mac'] for client in self.last_results]
|
||||
|
||||
def get_device_name(self, device):
|
||||
""" Returns the name of the given device or None if we don't know. """
|
||||
"""Return the name of the given device or None if we don't know."""
|
||||
if not self.last_results:
|
||||
return None
|
||||
for client in self.last_results:
|
||||
@@ -77,9 +72,9 @@ class ArubaDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the Aruba Access Point is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""Ensure the information from the Aruba Access Point is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
return False
|
||||
@@ -93,8 +88,7 @@ class ArubaDeviceScanner(object):
|
||||
return True
|
||||
|
||||
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
|
||||
connect = "ssh {}@{}"
|
||||
ssh = pexpect.spawn(connect.format(self.username, self.host))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.asuswrt
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a ASUSWRT router for device
|
||||
presence.
|
||||
Support for ASUSWRT routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -39,7 +36,7 @@ _IP_NEIGH_REGEX = re.compile(
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@@ -51,12 +48,10 @@ def get_scanner(hass, config):
|
||||
|
||||
|
||||
class AsusWrtDeviceScanner(object):
|
||||
"""
|
||||
This class queries a router running ASUSWRT firmware
|
||||
for connected devices. Adapted from DD-WRT scanner.
|
||||
"""
|
||||
"""This class queries a router running ASUSWRT firmware."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = str(config[CONF_USERNAME])
|
||||
self.password = str(config[CONF_PASSWORD])
|
||||
@@ -65,20 +60,17 @@ class AsusWrtDeviceScanner(object):
|
||||
|
||||
self.last_results = {}
|
||||
|
||||
# Test the router is accessible
|
||||
# Test the router is accessible.
|
||||
data = self.get_asuswrt_data()
|
||||
self.success_init = data is not None
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device IDs.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
return [client['mac'] for client in self.last_results]
|
||||
|
||||
def get_device_name(self, device):
|
||||
""" Returns the name of the given device or None if we don't know. """
|
||||
"""Return the name of the given device or None if we don't know."""
|
||||
if not self.last_results:
|
||||
return None
|
||||
for client in self.last_results:
|
||||
@@ -88,9 +80,9 @@ class AsusWrtDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the ASUSWRT router is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""Ensure the information from the ASUSWRT router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
return False
|
||||
@@ -109,7 +101,7 @@ class AsusWrtDeviceScanner(object):
|
||||
return True
|
||||
|
||||
def get_asuswrt_data(self):
|
||||
""" Retrieve data from ASUSWRT and return parsed result. """
|
||||
"""Retrieve data from ASUSWRT and return parsed result."""
|
||||
try:
|
||||
telnet = telnetlib.Telnet(self.host)
|
||||
telnet.read_until(b'login: ')
|
||||
@@ -138,9 +130,8 @@ class AsusWrtDeviceScanner(object):
|
||||
_LOGGER.warning("Could not parse lease row: %s", lease)
|
||||
continue
|
||||
|
||||
# For leases where the client doesn't set a hostname, ensure
|
||||
# it is blank and not '*', which breaks the entity_id down
|
||||
# the line
|
||||
# For leases where the client doesn't set a hostname, ensure it is
|
||||
# blank and not '*', which breaks the entity_id down the line.
|
||||
host = match.group('host')
|
||||
if host == '*':
|
||||
host = ''
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.ddwrt
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a DD-WRT router for device
|
||||
presence.
|
||||
Support for DD-WRT routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_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
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@@ -43,12 +40,10 @@ def get_scanner(hass, config):
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class DdWrtDeviceScanner(object):
|
||||
"""
|
||||
This class queries a wireless router running DD-WRT firmware
|
||||
for connected devices. Adapted from Tomato scanner.
|
||||
"""
|
||||
"""This class queries a wireless router running DD-WRT firmware."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = config[CONF_USERNAME]
|
||||
self.password = config[CONF_PASSWORD]
|
||||
@@ -65,19 +60,15 @@ class DdWrtDeviceScanner(object):
|
||||
self.success_init = data is not None
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
|
||||
return self.last_results
|
||||
|
||||
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:
|
||||
# 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:
|
||||
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
|
||||
data = self.get_ddwrt_data(url)
|
||||
@@ -90,15 +81,15 @@ class DdWrtDeviceScanner(object):
|
||||
if not dhcp_leases:
|
||||
return None
|
||||
|
||||
# remove leading and trailing single quotes
|
||||
# Remove leading and trailing single quotes.
|
||||
cleaned_str = dhcp_leases.strip().strip('"')
|
||||
elements = cleaned_str.split('","')
|
||||
num_clients = int(len(elements)/5)
|
||||
self.mac2name = {}
|
||||
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
|
||||
# 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
|
||||
if mac_index < len(elements):
|
||||
mac = elements[mac_index]
|
||||
@@ -108,9 +99,9 @@ class DdWrtDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the DD-WRT router is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""Ensure the information from the DD-WRT router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
return False
|
||||
@@ -135,7 +126,7 @@ class DdWrtDeviceScanner(object):
|
||||
# regex's out values so I guess I have to do the same,
|
||||
# LAME!!!
|
||||
|
||||
# remove leading and trailing single quotes
|
||||
# Remove leading and trailing single quotes.
|
||||
clean_str = active_clients.strip().strip("'")
|
||||
elements = clean_str.split("','")
|
||||
|
||||
@@ -145,7 +136,7 @@ class DdWrtDeviceScanner(object):
|
||||
return True
|
||||
|
||||
def get_ddwrt_data(self, url):
|
||||
""" Retrieve data from DD-WRT and return parsed result. """
|
||||
"""Retrieve data from DD-WRT and return parsed result."""
|
||||
try:
|
||||
response = requests.get(
|
||||
url,
|
||||
@@ -167,7 +158,7 @@ class DdWrtDeviceScanner(object):
|
||||
|
||||
|
||||
def _parse_ddwrt_response(data_str):
|
||||
""" Parse the DD-WRT data format. """
|
||||
"""Parse the DD-WRT data format."""
|
||||
return {
|
||||
key: val for key, val in _DDWRT_DATA_REGEX
|
||||
.findall(data_str)}
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.demo
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Demo platform for the device tracker.
|
||||
|
||||
device_tracker:
|
||||
platform: demo
|
||||
"""
|
||||
"""Demo platform for the device tracker."""
|
||||
import random
|
||||
|
||||
from homeassistant.components.device_tracker import DOMAIN
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see):
|
||||
""" Set up a demo tracker. """
|
||||
|
||||
"""Setup the demo tracker."""
|
||||
def offset():
|
||||
""" Return random offset. """
|
||||
"""Return random offset."""
|
||||
return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1))
|
||||
|
||||
def random_see(dev_id, name):
|
||||
""" Randomize a sighting. """
|
||||
"""Randomize a sighting."""
|
||||
see(
|
||||
dev_id=dev_id,
|
||||
host_name=name,
|
||||
@@ -30,7 +22,7 @@ def setup_scanner(hass, config, see):
|
||||
)
|
||||
|
||||
def observe(call=None):
|
||||
""" Observe three entities. """
|
||||
"""Observe three entities."""
|
||||
random_see('demo_paulus', 'Paulus')
|
||||
random_see('demo_anne_therese', 'Anne Therese')
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.fritz
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a FRITZ!Box router for device
|
||||
presence.
|
||||
Support for FRITZ!Box routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.fritz/
|
||||
@@ -17,15 +14,14 @@ from homeassistant.util import Throttle
|
||||
|
||||
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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def get_scanner(hass, config):
|
||||
""" Validates config and returns FritzBoxScanner. """
|
||||
"""Validate the configuration and return FritzBoxScanner."""
|
||||
if not validate_config(config,
|
||||
{DOMAIN: []},
|
||||
_LOGGER):
|
||||
@@ -37,22 +33,12 @@ def get_scanner(hass, config):
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class FritzBoxScanner(object):
|
||||
"""
|
||||
This class queries a FRITZ!Box router. It is using the
|
||||
fritzconnection library for communication with the router.
|
||||
"""This class queries a FRITZ!Box 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):
|
||||
"""Initialize the scanner."""
|
||||
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.password = ''
|
||||
self.success_init = True
|
||||
@@ -68,7 +54,7 @@ class FritzBoxScanner(object):
|
||||
if CONF_PASSWORD in config.keys():
|
||||
self.password = config[CONF_PASSWORD]
|
||||
|
||||
# Establish a connection to the FRITZ!Box
|
||||
# Establish a connection to the FRITZ!Box.
|
||||
try:
|
||||
self.fritz_box = fc.FritzHosts(address=self.host,
|
||||
user=self.username,
|
||||
@@ -77,7 +63,7 @@ class FritzBoxScanner(object):
|
||||
self.fritz_box = None
|
||||
|
||||
# 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:
|
||||
self.success_init = False
|
||||
|
||||
@@ -90,7 +76,7 @@ class FritzBoxScanner(object):
|
||||
"with IP: %s", self.host)
|
||||
|
||||
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()
|
||||
active_hosts = []
|
||||
for known_host in self.last_results:
|
||||
@@ -99,7 +85,7 @@ class FritzBoxScanner(object):
|
||||
return active_hosts
|
||||
|
||||
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"]
|
||||
if ret == {}:
|
||||
return None
|
||||
@@ -107,7 +93,7 @@ class FritzBoxScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
""" Retrieves latest information from the FRITZ!Box. """
|
||||
"""Retrieve latest information from the FRITZ!Box."""
|
||||
if not self.success_init:
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.icloud
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning iCloud devices.
|
||||
Support for iCloud connected devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.icloud/
|
||||
@@ -14,19 +12,19 @@ from homeassistant.helpers.event import track_utc_time_change
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['pyicloud==0.7.2']
|
||||
REQUIREMENTS = ['pyicloud==0.8.1']
|
||||
|
||||
CONF_INTERVAL = 'interval'
|
||||
DEFAULT_INTERVAL = 8
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see):
|
||||
""" Set up the iCloud Scanner. """
|
||||
"""Setup the iCloud Scanner."""
|
||||
from pyicloud import PyiCloudService
|
||||
from pyicloud.exceptions import PyiCloudFailedLoginException
|
||||
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)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
@@ -45,14 +43,14 @@ def setup_scanner(hass, config, see):
|
||||
return False
|
||||
|
||||
def keep_alive(now):
|
||||
""" Keeps authenticating iCloud connection. """
|
||||
"""Keep authenticating iCloud connection."""
|
||||
api.authenticate()
|
||||
_LOGGER.info("Authenticate against iCloud")
|
||||
|
||||
track_utc_time_change(hass, keep_alive, second=0)
|
||||
|
||||
def update_icloud(now):
|
||||
""" Authenticate against iCloud and scan for devices. """
|
||||
"""Authenticate against iCloud and scan for devices."""
|
||||
try:
|
||||
# The session timeouts if we are not using it so we
|
||||
# have to re-authenticate. This will send an email.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.locative
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Locative platform for the device tracker.
|
||||
Support for the Locative platform.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.locative/
|
||||
@@ -20,12 +18,10 @@ URL_API_LOCATIVE_ENDPOINT = "/api/locative"
|
||||
|
||||
|
||||
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
|
||||
# since Locative sends the data as key1=value1&key2=value2
|
||||
# in the request body, while Home Assistant expects json there.
|
||||
|
||||
hass.http.register_path(
|
||||
'GET', URL_API_LOCATIVE_ENDPOINT,
|
||||
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):
|
||||
""" Locative message received. """
|
||||
|
||||
"""Locative message received."""
|
||||
if not _check_data(handler, data):
|
||||
return
|
||||
|
||||
@@ -76,6 +71,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data):
|
||||
|
||||
|
||||
def _check_data(handler, data):
|
||||
"""Check the data."""
|
||||
if 'latitude' not in data or 'longitude' not in data:
|
||||
handler.write_text("Latitude and longitude not specified.",
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.luci
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a OpenWRT router for device
|
||||
presence.
|
||||
Support for OpenWRT (luci) routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@@ -40,20 +37,13 @@ def get_scanner(hass, config):
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class LuciDeviceScanner(object):
|
||||
"""
|
||||
This class queries a wireless router running OpenWrt firmware
|
||||
for connected devices. Adapted from Tomato scanner.
|
||||
"""This class queries a wireless router running OpenWrt firmware.
|
||||
|
||||
# opkg install luci-mod-rpc
|
||||
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.)
|
||||
Adapted from Tomato scanner.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
host = config[CONF_HOST]
|
||||
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
|
||||
|
||||
@@ -70,17 +60,12 @@ class LuciDeviceScanner(object):
|
||||
self.success_init = self.token is not None
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
|
||||
return self.last_results
|
||||
|
||||
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:
|
||||
if self.mac2name is None:
|
||||
url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host)
|
||||
@@ -100,8 +85,8 @@ class LuciDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the Luci router is up to date.
|
||||
"""Ensure the information from the Luci router is up to date.
|
||||
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
@@ -127,7 +112,7 @@ class LuciDeviceScanner(object):
|
||||
|
||||
|
||||
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})
|
||||
try:
|
||||
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):
|
||||
""" Get authentication token for the given host+username+password. """
|
||||
"""Get authentication token for the given host+username+password."""
|
||||
url = 'http://{}/cgi-bin/luci/rpc/auth'.format(host)
|
||||
return _req_json_rpc(url, 'login', username, password)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.mqtt
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
MQTT platform for the device tracker.
|
||||
Support for tracking MQTT enabled devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.mqtt/
|
||||
@@ -22,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see):
|
||||
""" Set up a MQTT tracker. """
|
||||
"""Setup the MQTT tracker."""
|
||||
devices = config.get(CONF_DEVICES)
|
||||
qos = util.convert(config.get(CONF_QOS), int, DEFAULT_QOS)
|
||||
|
||||
@@ -34,7 +32,7 @@ def setup_scanner(hass, config, see):
|
||||
dev_id_lookup = {}
|
||||
|
||||
def device_tracker_message_received(topic, payload, qos):
|
||||
""" MQTT message received. """
|
||||
"""MQTT message received."""
|
||||
see(dev_id=dev_id_lookup[topic], location_name=payload)
|
||||
|
||||
for dev_id, topic in devices.items():
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.netgear
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a Netgear router for device
|
||||
presence.
|
||||
Support for Netgear routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -23,7 +20,7 @@ REQUIREMENTS = ['pynetgear==0.3.2']
|
||||
|
||||
|
||||
def get_scanner(hass, config):
|
||||
""" Validates config and returns a Netgear scanner. """
|
||||
"""Validate the configuration and returns a Netgear scanner."""
|
||||
info = config[DOMAIN]
|
||||
host = info.get(CONF_HOST)
|
||||
username = info.get(CONF_USERNAME)
|
||||
@@ -39,9 +36,10 @@ def get_scanner(hass, config):
|
||||
|
||||
|
||||
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):
|
||||
"""Initialize the scanner."""
|
||||
import pynetgear
|
||||
|
||||
self.last_results = []
|
||||
@@ -66,15 +64,13 @@ class NetgearDeviceScanner(object):
|
||||
_LOGGER.error("Failed to Login")
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
|
||||
return (device.mac for device in self.last_results)
|
||||
|
||||
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:
|
||||
return next(device.name for device in self.last_results
|
||||
if device.mac == mac)
|
||||
@@ -83,8 +79,8 @@ class NetgearDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Retrieves latest information from the Netgear router.
|
||||
"""Retrieve latest information from the Netgear router.
|
||||
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
@@ -93,4 +89,9 @@ class NetgearDeviceScanner(object):
|
||||
with self.lock:
|
||||
_LOGGER.info("Scanning")
|
||||
|
||||
self.last_results = self._api.get_attached_devices() or []
|
||||
results = self._api.get_attached_devices()
|
||||
|
||||
if results is None:
|
||||
_LOGGER.warning('Error scanning devices')
|
||||
|
||||
self.last_results = results or []
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.nmap
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a network with nmap.
|
||||
Support for scanning a network with nmap.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.nmap_scanner/
|
||||
@@ -26,11 +24,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# interval in minutes to exclude devices from a scan while they are home
|
||||
CONF_HOME_INTERVAL = "home_interval"
|
||||
|
||||
REQUIREMENTS = ['python-nmap==0.4.3']
|
||||
REQUIREMENTS = ['python-nmap==0.6.0']
|
||||
|
||||
|
||||
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]},
|
||||
_LOGGER):
|
||||
return None
|
||||
@@ -43,7 +41,7 @@ Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
|
||||
|
||||
|
||||
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]
|
||||
arp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
out, _ = arp.communicate()
|
||||
@@ -55,9 +53,10 @@ def _arp(ip_address):
|
||||
|
||||
|
||||
class NmapDeviceScanner(object):
|
||||
""" This class scans for devices using nmap. """
|
||||
"""This class scans for devices using nmap."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.last_results = []
|
||||
|
||||
self.hosts = config[CONF_HOSTS]
|
||||
@@ -68,17 +67,13 @@ class NmapDeviceScanner(object):
|
||||
_LOGGER.info("nmap scanner initialized")
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
|
||||
return [device.mac for device in self.last_results]
|
||||
|
||||
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
|
||||
if device.mac == mac]
|
||||
|
||||
@@ -89,8 +84,8 @@ class NmapDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Scans the network for devices.
|
||||
"""Scan the network for devices.
|
||||
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
_LOGGER.info("Scanning")
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.owntracks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
OwnTracks platform for the device tracker.
|
||||
Support the OwnTracks platform.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.owntracks/
|
||||
@@ -28,13 +26,15 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
LOCK = threading.Lock()
|
||||
|
||||
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
|
||||
|
||||
|
||||
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):
|
||||
""" MQTT message received. """
|
||||
|
||||
"""MQTT message received."""
|
||||
# Docs on available data:
|
||||
# http://owntracks.org/booklet/tech/json/#_typelocation
|
||||
try:
|
||||
@@ -45,7 +45,9 @@ def setup_scanner(hass, config, see):
|
||||
'Unable to parse payload as JSON: %s', payload)
|
||||
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
|
||||
|
||||
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):
|
||||
# pylint: disable=too-many-branches, too-many-statements
|
||||
""" MQTT event (geofences) received. """
|
||||
|
||||
"""MQTT event (geofences) received."""
|
||||
# Docs on available data:
|
||||
# http://owntracks.org/booklet/tech/json/#_typetransition
|
||||
try:
|
||||
@@ -98,14 +99,11 @@ def setup_scanner(hass, config, see):
|
||||
_LOGGER.info("Added beacon %s", location)
|
||||
else:
|
||||
# Normal region
|
||||
if not zone.attributes.get('passive'):
|
||||
kwargs['location_name'] = location
|
||||
|
||||
regions = REGIONS_ENTERED[dev_id]
|
||||
if location not in regions:
|
||||
regions.append(location)
|
||||
_LOGGER.info("Enter region %s", location)
|
||||
_set_gps_from_zone(kwargs, zone)
|
||||
_set_gps_from_zone(kwargs, location, zone)
|
||||
|
||||
see(**kwargs)
|
||||
see_beacons(dev_id, kwargs)
|
||||
@@ -120,16 +118,22 @@ def setup_scanner(hass, config, see):
|
||||
if new_region:
|
||||
# Exit to previous region
|
||||
zone = hass.states.get("zone.{}".format(new_region))
|
||||
if not zone.attributes.get('passive'):
|
||||
kwargs['location_name'] = new_region
|
||||
_set_gps_from_zone(kwargs, zone)
|
||||
_set_gps_from_zone(kwargs, new_region, zone)
|
||||
_LOGGER.info("Exit to %s", new_region)
|
||||
see(**kwargs)
|
||||
see_beacons(dev_id, kwargs)
|
||||
|
||||
else:
|
||||
_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_beacons(dev_id, kwargs)
|
||||
see(**kwargs)
|
||||
see_beacons(dev_id, kwargs)
|
||||
else:
|
||||
_LOGGER.info("Inaccurate GPS reported")
|
||||
|
||||
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
|
||||
if location in beacons:
|
||||
@@ -143,8 +147,7 @@ def setup_scanner(hass, config, see):
|
||||
return
|
||||
|
||||
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()
|
||||
# the battery state applies to the tracking device, not the beacon
|
||||
kwargs.pop('battery', None)
|
||||
@@ -154,16 +157,13 @@ def setup_scanner(hass, config, see):
|
||||
see(**kwargs)
|
||||
|
||||
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
|
||||
|
||||
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _parse_see_args(topic, data):
|
||||
""" Parse the OwnTracks location parameters,
|
||||
into the format see expects. """
|
||||
|
||||
"""Parse the OwnTracks location parameters, into the format see expects."""
|
||||
parts = topic.split('/')
|
||||
dev_id = '{}_{}'.format(parts[1], parts[2])
|
||||
host_name = parts[1]
|
||||
@@ -179,12 +179,12 @@ def _parse_see_args(topic, data):
|
||||
return dev_id, kwargs
|
||||
|
||||
|
||||
def _set_gps_from_zone(kwargs, zone):
|
||||
""" Set the see parameters from the zone parameters """
|
||||
|
||||
def _set_gps_from_zone(kwargs, location, zone):
|
||||
"""Set the see parameters from the zone parameters."""
|
||||
if zone is not None:
|
||||
kwargs['gps'] = (
|
||||
zone.attributes['latitude'],
|
||||
zone.attributes['longitude'])
|
||||
kwargs['gps_accuracy'] = zone.attributes['radius']
|
||||
kwargs['location_name'] = location
|
||||
return kwargs
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.snmp
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports fetching WiFi associations
|
||||
through SNMP.
|
||||
Support for fetching WiFi associations through SNMP.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -29,7 +26,7 @@ CONF_BASEOID = "baseoid"
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]},
|
||||
_LOGGER):
|
||||
@@ -41,10 +38,10 @@ def get_scanner(hass, config):
|
||||
|
||||
|
||||
class SnmpScanner(object):
|
||||
"""
|
||||
This class queries any SNMP capable Acces Point for connected devices.
|
||||
"""
|
||||
"""Queries any SNMP capable Access Point for connected devices."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
from pysnmp.entity.rfc3413.oneliner import cmdgen
|
||||
self.snmp = cmdgen.CommandGenerator()
|
||||
|
||||
@@ -61,25 +58,23 @@ class SnmpScanner(object):
|
||||
self.success_init = data is not None
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device IDs.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
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
|
||||
# pylint: disable=R0201
|
||||
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
|
||||
return None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the WAP is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""Ensure the information from the WAP is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
return False
|
||||
@@ -93,8 +88,7 @@ class SnmpScanner(object):
|
||||
return True
|
||||
|
||||
def get_snmp_data(self):
|
||||
""" Fetch mac addresses from WAP via SNMP. """
|
||||
|
||||
"""Fetch MAC addresses from WAP via SNMP."""
|
||||
devices = []
|
||||
|
||||
errindication, errstatus, errindex, restable = self.snmp.nextCmd(
|
||||
@@ -111,6 +105,7 @@ class SnmpScanner(object):
|
||||
for resrow in restable:
|
||||
for _, val in resrow:
|
||||
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)])
|
||||
devices.append({'mac': mac})
|
||||
return devices
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.thomson
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a THOMSON router for device
|
||||
presence.
|
||||
Support for THOMSON routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -35,7 +32,7 @@ _DEVICES_REGEX = re.compile(
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@@ -47,12 +44,10 @@ def get_scanner(hass, config):
|
||||
|
||||
|
||||
class ThomsonDeviceScanner(object):
|
||||
"""
|
||||
This class queries a router running THOMSON firmware
|
||||
for connected devices. Adapted from ASUSWRT scanner.
|
||||
"""
|
||||
"""This class queries a router running THOMSON firmware."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = config[CONF_USERNAME]
|
||||
self.password = config[CONF_PASSWORD]
|
||||
@@ -61,20 +56,17 @@ class ThomsonDeviceScanner(object):
|
||||
|
||||
self.last_results = {}
|
||||
|
||||
# Test the router is accessible
|
||||
# Test the router is accessible.
|
||||
data = self.get_thomson_data()
|
||||
self.success_init = data is not None
|
||||
|
||||
def scan_devices(self):
|
||||
""" Scans for new devices and return a
|
||||
list containing found device ids. """
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
return [client['mac'] for client in self.last_results]
|
||||
|
||||
def get_device_name(self, device):
|
||||
""" Returns the name of the given device
|
||||
or None if we don't know. """
|
||||
"""Return the name of the given device or None if we don't know."""
|
||||
if not self.last_results:
|
||||
return None
|
||||
for client in self.last_results:
|
||||
@@ -84,9 +76,9 @@ class ThomsonDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the THOMSON router is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""Ensure the information from the THOMSON router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
return False
|
||||
@@ -97,14 +89,14 @@ class ThomsonDeviceScanner(object):
|
||||
if not data:
|
||||
return False
|
||||
|
||||
# flag C stands for CONNECTED
|
||||
# Flag C stands for CONNECTED
|
||||
active_clients = [client for client in data.values() if
|
||||
client['status'].find('C') != -1]
|
||||
self.last_results = active_clients
|
||||
return True
|
||||
|
||||
def get_thomson_data(self):
|
||||
""" Retrieve data from THOMSON and return parsed result. """
|
||||
"""Retrieve data from THOMSON and return parsed result."""
|
||||
try:
|
||||
telnet = telnetlib.Telnet(self.host)
|
||||
telnet.read_until(b'Username : ')
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.tomato
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a Tomato router for device
|
||||
presence.
|
||||
Support for Tomato routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
CONF_HTTP_ID = "http_id"
|
||||
@@ -29,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME,
|
||||
CONF_PASSWORD, CONF_HTTP_ID]},
|
||||
@@ -40,14 +37,10 @@ def get_scanner(hass, config):
|
||||
|
||||
|
||||
class TomatoDeviceScanner(object):
|
||||
""" 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/
|
||||
"""
|
||||
"""This class queries a wireless router running Tomato firmware."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
host, http_id = config[CONF_HOST], config[CONF_HTTP_ID]
|
||||
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
|
||||
|
||||
@@ -68,16 +61,13 @@ class TomatoDeviceScanner(object):
|
||||
self.success_init = self._update_tomato_info()
|
||||
|
||||
def scan_devices(self):
|
||||
""" Scans for new devices and return a
|
||||
list containing found device ids. """
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_tomato_info()
|
||||
|
||||
return [item[1] for item in self.last_results['wldev']]
|
||||
|
||||
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']
|
||||
if item[2] == device]
|
||||
|
||||
@@ -88,19 +78,17 @@ class TomatoDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_tomato_info(self):
|
||||
""" Ensures the information from the Tomato router is up to date.
|
||||
Returns boolean if scanning successful. """
|
||||
"""Ensure the information from the Tomato router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
with self.lock:
|
||||
self.logger.info("Scanning")
|
||||
|
||||
try:
|
||||
response = requests.Session().send(self.req, timeout=3)
|
||||
|
||||
# Calling and parsing the Tomato api here. We only need the
|
||||
# wldev and dhcpd_lease values. For API description see:
|
||||
# http://paulusschoutsen.nl/
|
||||
# blog/2013/10/tomato-api-documentation/
|
||||
# wldev and dhcpd_lease values.
|
||||
if response.status_code == 200:
|
||||
|
||||
for param, value in \
|
||||
@@ -109,7 +97,6 @@ class TomatoDeviceScanner(object):
|
||||
if param == 'wldev' or param == 'dhcpd_lease':
|
||||
self.last_results[param] = \
|
||||
json.loads(value.replace("'", '"'))
|
||||
|
||||
return True
|
||||
|
||||
elif response.status_code == 401:
|
||||
@@ -117,29 +104,25 @@ class TomatoDeviceScanner(object):
|
||||
self.logger.exception((
|
||||
"Failed to authenticate, "
|
||||
"please check your username and password"))
|
||||
|
||||
return False
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
# 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((
|
||||
"Failed to connect to the router"
|
||||
" or invalid http_id supplied"))
|
||||
|
||||
return False
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
# 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(
|
||||
"Connection to the router timed out")
|
||||
|
||||
return False
|
||||
|
||||
except ValueError:
|
||||
# If json decoder could not parse the response
|
||||
# If JSON decoder could not parse the response.
|
||||
self.logger.exception(
|
||||
"Failed to parse response from router")
|
||||
|
||||
return False
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.tplink
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a TP-Link router for device
|
||||
presence.
|
||||
Support for TP-Link routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.tplink/
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
import re
|
||||
import threading
|
||||
@@ -27,30 +25,31 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
return None
|
||||
|
||||
scanner = Tplink3DeviceScanner(config[DOMAIN])
|
||||
scanner = Tplink4DeviceScanner(config[DOMAIN])
|
||||
|
||||
if not scanner.success_init:
|
||||
scanner = Tplink3DeviceScanner(config[DOMAIN])
|
||||
|
||||
if not scanner.success_init:
|
||||
scanner = Tplink2DeviceScanner(config[DOMAIN])
|
||||
|
||||
if not scanner.success_init:
|
||||
scanner = TplinkDeviceScanner(config[DOMAIN])
|
||||
if not scanner.success_init:
|
||||
scanner = TplinkDeviceScanner(config[DOMAIN])
|
||||
|
||||
return scanner if scanner.success_init else None
|
||||
|
||||
|
||||
class TplinkDeviceScanner(object):
|
||||
"""
|
||||
This class queries a wireless router running TP-Link firmware
|
||||
for connected devices.
|
||||
"""
|
||||
"""This class queries a wireless router running TP-Link firmware."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
host = config[CONF_HOST]
|
||||
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
|
||||
|
||||
@@ -66,29 +65,21 @@ class TplinkDeviceScanner(object):
|
||||
self.success_init = self._update_info()
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""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 TP-Link firmware doesn't save the name of the wireless device.
|
||||
"""
|
||||
|
||||
"""The firmware doesn't save the name of the wireless device."""
|
||||
return None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the TP-Link router is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
"""Ensure the information from the TP-Link router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
with self.lock:
|
||||
_LOGGER.info("Loading wireless clients...")
|
||||
|
||||
@@ -107,34 +98,24 @@ class TplinkDeviceScanner(object):
|
||||
|
||||
|
||||
class Tplink2DeviceScanner(TplinkDeviceScanner):
|
||||
"""
|
||||
This class queries a wireless router running newer version of TP-Link
|
||||
firmware for connected devices.
|
||||
"""
|
||||
"""This class queries a router with newer version of TP-Link firmware."""
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
return self.last_results.keys()
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""
|
||||
The TP-Link firmware doesn't save the name of the wireless device.
|
||||
"""
|
||||
|
||||
"""The firmware doesn't save the name of the wireless device."""
|
||||
return self.last_results.get(device)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the TP-Link router is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
"""Ensure the information from the TP-Link router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
with self.lock:
|
||||
_LOGGER.info("Loading wireless clients...")
|
||||
|
||||
@@ -172,46 +153,36 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
|
||||
|
||||
|
||||
class Tplink3DeviceScanner(TplinkDeviceScanner):
|
||||
"""
|
||||
This class queries the Archer C9 router running version 150811 or higher
|
||||
of TP-Link firmware for connected devices.
|
||||
"""
|
||||
"""This class queries the Archer C9 router with version 150811 or high."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.stok = ''
|
||||
self.sysauth = ''
|
||||
super(Tplink3DeviceScanner, self).__init__(config)
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
return self.last_results.keys()
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def get_device_name(self, device):
|
||||
"""
|
||||
The TP-Link firmware doesn't save the name of the wireless device.
|
||||
"""The firmware doesn't save the name of the wireless device.
|
||||
|
||||
We are forced to use the MAC address as name here.
|
||||
"""
|
||||
|
||||
return self.last_results.get(device)
|
||||
|
||||
def _get_auth_tokens(self):
|
||||
"""
|
||||
Retrieves auth tokens from the router.
|
||||
"""
|
||||
|
||||
"""Retrieve auth tokens from the router."""
|
||||
_LOGGER.info("Retrieving auth tokens...")
|
||||
|
||||
url = 'http://{}/cgi-bin/luci/;stok=/login?form=login' \
|
||||
.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,
|
||||
params={'operation': 'login',
|
||||
'username': self.username,
|
||||
@@ -232,11 +203,10 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the TP-Link router is up to date.
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
"""Ensure the information from the TP-Link router is up to date.
|
||||
|
||||
Return boolean if scanning successful.
|
||||
"""
|
||||
with self.lock:
|
||||
if (self.stok == '') or (self.sysauth == ''):
|
||||
self._get_auth_tokens()
|
||||
@@ -281,3 +251,81 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.ubus
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a OpenWRT router for device
|
||||
presence.
|
||||
Support for OpenWRT (ubus) routers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
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.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)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
@@ -41,23 +38,13 @@ def get_scanner(hass, config):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class UbusDeviceScanner(object):
|
||||
"""
|
||||
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
|
||||
This class queries a wireless router running OpenWrt firmware.
|
||||
|
||||
Adapted from Tomato scanner.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
host = config[CONF_HOST]
|
||||
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
|
||||
|
||||
@@ -73,17 +60,12 @@ class UbusDeviceScanner(object):
|
||||
self.success_init = self.session_id is not None
|
||||
|
||||
def scan_devices(self):
|
||||
"""
|
||||
Scans for new devices and return a list containing found device ids.
|
||||
"""
|
||||
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
self._update_info()
|
||||
|
||||
return self.last_results
|
||||
|
||||
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:
|
||||
if self.leasefile is None:
|
||||
result = _req_json_rpc(self.url, self.session_id,
|
||||
@@ -112,8 +94,8 @@ class UbusDeviceScanner(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||
def _update_info(self):
|
||||
"""
|
||||
Ensures the information from the Luci router is up to date.
|
||||
"""Ensure the information from the Luci router is up to date.
|
||||
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
if not self.success_init:
|
||||
@@ -141,8 +123,7 @@ class UbusDeviceScanner(object):
|
||||
|
||||
|
||||
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",
|
||||
"id": 1,
|
||||
"method": rpcmethod,
|
||||
@@ -167,7 +148,7 @@ def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
|
||||
|
||||
|
||||
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',
|
||||
'session', 'login', username=username,
|
||||
password=password)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.unifi
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Device tracker platform that supports scanning a Unifi WAP controller
|
||||
Support for Unifi WAP controllers.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.unifi/
|
||||
"""
|
||||
import logging
|
||||
import urllib
|
||||
@@ -17,7 +18,7 @@ CONF_PORT = 'port'
|
||||
|
||||
|
||||
def get_scanner(hass, config):
|
||||
""" Sets up unifi device_tracker """
|
||||
"""Setup Unifi device_tracker."""
|
||||
from unifi.controller import Controller
|
||||
|
||||
if not validate_config(config, {DOMAIN: [CONF_USERNAME,
|
||||
@@ -50,10 +51,12 @@ class UnifiScanner(object):
|
||||
"""Provide device_tracker support from Unifi WAP client data."""
|
||||
|
||||
def __init__(self, controller):
|
||||
"""Initialize the scanner."""
|
||||
self._controller = controller
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
"""Get the clients from the device."""
|
||||
try:
|
||||
clients = self._controller.get_clients()
|
||||
except urllib.error.HTTPError as ex:
|
||||
@@ -63,12 +66,12 @@ class UnifiScanner(object):
|
||||
self._clients = {client['mac']: client for client in clients}
|
||||
|
||||
def scan_devices(self):
|
||||
""" Scans for devices. """
|
||||
"""Scan for devices."""
|
||||
self._update()
|
||||
return self._clients.keys()
|
||||
|
||||
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
|
||||
return the hostname if it has been detected.
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
EVENT_PLATFORM_DISCOVERED)
|
||||
|
||||
DOMAIN = "discovery"
|
||||
REQUIREMENTS = ['netdisco==0.5.2']
|
||||
REQUIREMENTS = ['netdisco==0.5.5']
|
||||
|
||||
SCAN_INTERVAL = 300 # seconds
|
||||
|
||||
@@ -25,6 +25,7 @@ SERVICE_CAST = 'google_cast'
|
||||
SERVICE_NETGEAR = 'netgear_router'
|
||||
SERVICE_SONOS = 'sonos'
|
||||
SERVICE_PLEX = 'plex_mediaserver'
|
||||
SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
|
||||
|
||||
SERVICE_HANDLERS = {
|
||||
SERVICE_WEMO: "wemo",
|
||||
@@ -33,6 +34,7 @@ SERVICE_HANDLERS = {
|
||||
SERVICE_NETGEAR: 'device_tracker',
|
||||
SERVICE_SONOS: 'media_player',
|
||||
SERVICE_PLEX: 'media_player',
|
||||
SERVICE_SQUEEZEBOX: 'media_player',
|
||||
}
|
||||
|
||||
|
||||
@@ -55,10 +57,7 @@ def listen(hass, service, callback):
|
||||
|
||||
|
||||
def discover(hass, service, discovered=None, component=None, hass_config=None):
|
||||
"""Fire discovery event.
|
||||
|
||||
Can ensure a component is loaded.
|
||||
"""
|
||||
"""Fire discovery event. Can ensure a component is loaded."""
|
||||
if component is not None:
|
||||
bootstrap.setup_component(hass, component, hass_config)
|
||||
|
||||
@@ -73,7 +72,7 @@ def discover(hass, service, discovered=None, component=None, hass_config=None):
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Starts a discovery service. """
|
||||
"""Start a discovery service."""
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from netdisco.service import DiscoveryService
|
||||
@@ -84,13 +83,13 @@ def setup(hass, config):
|
||||
lock = threading.Lock()
|
||||
|
||||
def new_service_listener(service, info):
|
||||
""" Called when a new service is found. """
|
||||
"""Called when a new service is found."""
|
||||
with lock:
|
||||
logger.info("Found new service: %s %s", service, info)
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
@@ -105,7 +104,7 @@ def setup(hass, config):
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def start_discovery(event):
|
||||
""" Start discovering. """
|
||||
"""Start discovering."""
|
||||
netdisco = DiscoveryService(SCAN_INTERVAL)
|
||||
netdisco.add_listener(new_service_listener)
|
||||
netdisco.start()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.downloader
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides functionality to download files.
|
||||
Support for functionality to download files.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/downloader/
|
||||
@@ -28,8 +26,7 @@ CONF_DOWNLOAD_DIR = 'download_dir'
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def setup(hass, config):
|
||||
""" Listens for download events to download files. """
|
||||
|
||||
"""Listen for download events to download files."""
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
|
||||
@@ -50,14 +47,13 @@ def setup(hass, config):
|
||||
return False
|
||||
|
||||
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:
|
||||
logger.error("Service called but 'url' parameter not specified.")
|
||||
return
|
||||
|
||||
def do_download():
|
||||
""" Downloads the file. """
|
||||
"""Download the file."""
|
||||
try:
|
||||
url = service.data[ATTR_URL]
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.ecobee
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Ecobee component
|
||||
Support for Ecobee.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/ecobee/
|
||||
@@ -31,12 +29,12 @@ _LOGGER = logging.getLogger(__name__)
|
||||
ECOBEE_CONFIG_FILE = 'ecobee.conf'
|
||||
_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)
|
||||
|
||||
|
||||
def request_configuration(network, hass, config):
|
||||
""" Request configuration steps from the user. """
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = get_component('configurator')
|
||||
if 'ecobee' in _CONFIGURING:
|
||||
configurator.notify_errors(
|
||||
@@ -46,7 +44,7 @@ def request_configuration(network, hass, config):
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
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.update()
|
||||
setup_ecobee(hass, network, config)
|
||||
@@ -62,7 +60,7 @@ def request_configuration(network, hass, 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 network.pin is not None:
|
||||
request_configuration(network, hass, config)
|
||||
@@ -93,22 +91,23 @@ def setup_ecobee(hass, network, config):
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class EcobeeData(object):
|
||||
""" Gets the latest data and update the states. """
|
||||
"""Get the latest data and update the states."""
|
||||
|
||||
def __init__(self, config_file):
|
||||
"""Initialize the Ecobee data object."""
|
||||
from pyecobee import Ecobee
|
||||
self.ecobee = Ecobee(config_file)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
""" Get the latest data from pyecobee. """
|
||||
"""Get the latest data from pyecobee."""
|
||||
self.ecobee.update()
|
||||
_LOGGER.info("ecobee data updated successfully.")
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""
|
||||
Setup Ecobee.
|
||||
"""Setup Ecobee.
|
||||
|
||||
Will automatically load thermostat and sensor components to support
|
||||
devices discovered on the network.
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.frontend
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides a frontend for Home Assistant.
|
||||
"""
|
||||
"""Handle the frontend for Home Assistant."""
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
@@ -32,7 +27,7 @@ _FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup serving the frontend. """
|
||||
"""Setup serving the frontend."""
|
||||
for url in FRONTEND_URLS:
|
||||
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):
|
||||
""" Returns all data needed to bootstrap Home Assistant. """
|
||||
"""Return all data needed to bootstrap Home Assistant."""
|
||||
hass = handler.server.hass
|
||||
|
||||
handler.write_json({
|
||||
@@ -70,7 +65,7 @@ def _handle_get_api_bootstrap(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_header('Content-type', 'text/html; charset=utf-8')
|
||||
handler.end_headers()
|
||||
@@ -95,7 +90,7 @@ def _handle_get_root(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:
|
||||
sw_path = "home-assistant-polymer/build/service_worker.js"
|
||||
else:
|
||||
@@ -106,7 +101,7 @@ def _handle_get_service_worker(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'))
|
||||
|
||||
# Strip md5 hash out
|
||||
@@ -120,9 +115,7 @@ def _handle_get_static(handler, path_match, data):
|
||||
|
||||
|
||||
def _handle_get_local(handler, path_match, data):
|
||||
"""
|
||||
Returns a static file from the hass.config.path/www for the frontend.
|
||||
"""
|
||||
"""Return a static file from the hass.config.path/www for the frontend."""
|
||||
req_file = util.sanitize_path(path_match.group('file'))
|
||||
|
||||
path = handler.server.hass.config.path('www', req_file)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
""" DO NOT MODIFY. Auto-generated by update_mdi script """
|
||||
VERSION = "2f4adc5d3ad6d2f73bf69ed29b7594fd"
|
||||
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
|
||||
VERSION = "df49e6b7c930eb39b42ff1909712e95e"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "a4d021cb50ed079fcfda7369ed2f0d4a"
|
||||
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
||||
VERSION = "49974cb3bb443751f7548e4e3b353304"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -33,13 +33,13 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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
|
||||
return hass.states.is_state(entity_id, STATE_CLOSED)
|
||||
|
||||
|
||||
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
|
||||
hass.services.call(DOMAIN, SERVICE_CLOSE, data)
|
||||
|
||||
@@ -58,7 +58,7 @@ def setup(hass, config):
|
||||
component.setup(config)
|
||||
|
||||
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)
|
||||
|
||||
for item in target_locks:
|
||||
@@ -81,7 +81,8 @@ def setup(hass, config):
|
||||
|
||||
|
||||
class GarageDoorDevice(Entity):
|
||||
"""Represents a garage door."""
|
||||
"""Representation of a garage door."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@property
|
||||
def is_closed(self):
|
||||
@@ -98,7 +99,7 @@ class GarageDoorDevice(Entity):
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Returns the state of the garage door."""
|
||||
"""Return the state of the garage door."""
|
||||
closed = self.is_closed
|
||||
if closed is None:
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@@ -19,7 +19,9 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
||||
class DemoGarageDoor(GarageDoorDevice):
|
||||
"""Provides a demo garage door."""
|
||||
|
||||
def __init__(self, name, state):
|
||||
"""Initialize the garage door."""
|
||||
self._name = name
|
||||
self._state = state
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ import logging
|
||||
from homeassistant.components.garage_door import GarageDoorDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
||||
REQUIREMENTS = ['python-wink==0.6.2']
|
||||
REQUIREMENTS = ['python-wink==0.6.4']
|
||||
|
||||
|
||||
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
|
||||
|
||||
if discovery_info is None:
|
||||
@@ -32,19 +32,20 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class WinkGarageDoorDevice(GarageDoorDevice):
|
||||
"""Represents a Wink garage door."""
|
||||
"""Representation of a Wink garage door."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the garage door."""
|
||||
self.wink = wink
|
||||
|
||||
@property
|
||||
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())
|
||||
|
||||
@property
|
||||
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()
|
||||
|
||||
def update(self):
|
||||
@@ -53,11 +54,16 @@ class WinkGarageDoorDevice(GarageDoorDevice):
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Returns true if door is closed."""
|
||||
"""Return true if door is closed."""
|
||||
return self.wink.state() == 0
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def close_door(self):
|
||||
"""Closes the door."""
|
||||
"""Close the door."""
|
||||
self.wink.set_state(0)
|
||||
|
||||
def open_door(self):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Component that records all events and state changes and feeds the data to
|
||||
a Graphite installation.
|
||||
Component that sends data to aGraphite installation.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/graphite/
|
||||
@@ -35,8 +34,10 @@ def setup(hass, config):
|
||||
|
||||
|
||||
class GraphiteFeeder(threading.Thread):
|
||||
"""Feeds data to Graphite."""
|
||||
"""Feed data to Graphite."""
|
||||
|
||||
def __init__(self, hass, host, port, prefix):
|
||||
"""Initialize the feeder."""
|
||||
super(GraphiteFeeder, self).__init__(daemon=True)
|
||||
self._hass = hass
|
||||
self._host = host
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""
|
||||
homeassistant.components.group
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides functionality to group devices that can be turned on or off.
|
||||
Provides functionality to group entities.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/group/
|
||||
"""
|
||||
import threading
|
||||
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.const import (
|
||||
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):
|
||||
""" 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:
|
||||
if state in states:
|
||||
return states
|
||||
@@ -41,7 +41,7 @@ def _get_group_on_off(state):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if state:
|
||||
@@ -54,8 +54,7 @@ def is_on(hass, entity_id):
|
||||
|
||||
|
||||
def expand_entity_ids(hass, entity_ids):
|
||||
""" Returns the given list of entity ids and expands group ids into
|
||||
the entity ids it represents if found. """
|
||||
"""Return entity_ids with group entity ids replaced by their members."""
|
||||
found_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):
|
||||
""" Get the entity ids that make up this group. """
|
||||
"""Get members of this group."""
|
||||
entity_id = entity_id.lower()
|
||||
|
||||
try:
|
||||
@@ -107,7 +106,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
|
||||
|
||||
|
||||
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():
|
||||
if not isinstance(conf, dict):
|
||||
conf = {CONF_ENTITIES: conf}
|
||||
@@ -127,12 +126,12 @@ def setup(hass, config):
|
||||
|
||||
|
||||
class Group(Entity):
|
||||
""" Tracks a group of entity ids. """
|
||||
"""Track a group of entity ids."""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, too-many-arguments
|
||||
|
||||
def __init__(self, hass, name, entity_ids=None, user_defined=True,
|
||||
icon=None, view=False, object_id=None):
|
||||
"""Initialize a group."""
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self._state = STATE_UNKNOWN
|
||||
@@ -146,6 +145,7 @@ class Group(Entity):
|
||||
self.group_on = None
|
||||
self.group_off = None
|
||||
self._assumed_state = False
|
||||
self._lock = threading.Lock()
|
||||
|
||||
if entity_ids is not None:
|
||||
self.update_tracked_entity_ids(entity_ids)
|
||||
@@ -154,26 +154,32 @@ class Group(Entity):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No need to poll because groups will update themselves."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the group."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the group."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon of the group."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""If group should be hidden or not."""
|
||||
return not self._user_defined or self._view
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the state attributes for the group."""
|
||||
data = {
|
||||
ATTR_ENTITY_ID: self.tracking,
|
||||
ATTR_ORDER: self._order,
|
||||
@@ -186,11 +192,11 @@ class Group(Entity):
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
def update_tracked_entity_ids(self, entity_ids):
|
||||
""" Update the tracked entity IDs. """
|
||||
"""Update the member entity IDs."""
|
||||
self.stop()
|
||||
self.tracking = tuple(ent_id.lower() for ent_id in entity_ids)
|
||||
self.group_on, self.group_off = None, None
|
||||
@@ -200,30 +206,30 @@ class Group(Entity):
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
""" Starts the tracking. """
|
||||
"""Start tracking members."""
|
||||
track_state_change(
|
||||
self.hass, self.tracking, self._state_changed_listener)
|
||||
|
||||
def stop(self):
|
||||
""" Unregisters the group from Home Assistant. """
|
||||
"""Unregister the group from Home Assistant."""
|
||||
self.hass.states.remove(self.entity_id)
|
||||
|
||||
self.hass.bus.remove_listener(
|
||||
ha.EVENT_STATE_CHANGED, self._state_changed_listener)
|
||||
|
||||
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._update_group_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_ha_state()
|
||||
|
||||
@property
|
||||
def _tracking_states(self):
|
||||
"""States that the group is tracking."""
|
||||
"""The states that the group is tracking."""
|
||||
states = []
|
||||
|
||||
for entity_id in self.tracking:
|
||||
@@ -242,49 +248,53 @@ class Group(Entity):
|
||||
"""
|
||||
# pylint: disable=too-many-branches
|
||||
# To store current states of group entities. Might not be needed.
|
||||
states = None
|
||||
gr_state, gr_on, gr_off = self._state, self.group_on, self.group_off
|
||||
with self._lock:
|
||||
states = None
|
||||
gr_state = self._state
|
||||
gr_on = self.group_on
|
||||
gr_off = self.group_off
|
||||
|
||||
# We have not determined type of group yet
|
||||
if gr_on is None:
|
||||
if tr_state is None:
|
||||
states = self._tracking_states
|
||||
# We have not determined type of group yet
|
||||
if gr_on is None:
|
||||
if tr_state is None:
|
||||
states = self._tracking_states
|
||||
|
||||
for state in states:
|
||||
gr_on, gr_off = \
|
||||
_get_group_on_off(state.state)
|
||||
if gr_on is not None:
|
||||
break
|
||||
else:
|
||||
gr_on, gr_off = _get_group_on_off(tr_state.state)
|
||||
for state in states:
|
||||
gr_on, gr_off = \
|
||||
_get_group_on_off(state.state)
|
||||
if gr_on is not None:
|
||||
break
|
||||
else:
|
||||
gr_on, gr_off = _get_group_on_off(tr_state.state)
|
||||
|
||||
if gr_on is not None:
|
||||
self.group_on, self.group_off = gr_on, gr_off
|
||||
if gr_on is not None:
|
||||
self.group_on, self.group_off = gr_on, gr_off
|
||||
|
||||
# We cannot determine state of the group
|
||||
if gr_on is None:
|
||||
return
|
||||
# We cannot determine state of the group
|
||||
if gr_on is None:
|
||||
return
|
||||
|
||||
if tr_state is None or (gr_state == gr_on and
|
||||
tr_state.state == gr_off):
|
||||
if states is None:
|
||||
states = self._tracking_states
|
||||
if tr_state is None or (gr_state == gr_on and
|
||||
tr_state.state == gr_off):
|
||||
if states is None:
|
||||
states = self._tracking_states
|
||||
|
||||
if any(state.state == gr_on for state in states):
|
||||
self._state = gr_on
|
||||
else:
|
||||
self._state = gr_off
|
||||
if any(state.state == gr_on for state in states):
|
||||
self._state = gr_on
|
||||
else:
|
||||
self._state = gr_off
|
||||
|
||||
elif tr_state.state in (gr_on, gr_off):
|
||||
self._state = tr_state.state
|
||||
elif tr_state.state in (gr_on, gr_off):
|
||||
self._state = tr_state.state
|
||||
|
||||
if tr_state is None or self._assumed_state and \
|
||||
not tr_state.attributes.get(ATTR_ASSUMED_STATE):
|
||||
if states is None:
|
||||
states = self._tracking_states
|
||||
if tr_state is None or self._assumed_state and \
|
||||
not tr_state.attributes.get(ATTR_ASSUMED_STATE):
|
||||
if states is None:
|
||||
states = self._tracking_states
|
||||
|
||||
self._assumed_state = any(state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
for state in states)
|
||||
self._assumed_state = any(
|
||||
state.attributes.get(ATTR_ASSUMED_STATE) for state
|
||||
in states)
|
||||
|
||||
elif tr_state.attributes.get(ATTR_ASSUMED_STATE):
|
||||
self._assumed_state = True
|
||||
elif tr_state.attributes.get(ATTR_ASSUMED_STATE):
|
||||
self._assumed_state = True
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.history
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provide pre-made queries on top of the recorder component.
|
||||
|
||||
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 itertools import groupby
|
||||
|
||||
import homeassistant.components.recorder as recorder
|
||||
from homeassistant.components import recorder, script
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import HTTP_BAD_REQUEST
|
||||
|
||||
@@ -19,13 +17,14 @@ DOMAIN = 'history'
|
||||
DEPENDENCIES = ['recorder', 'http']
|
||||
|
||||
SIGNIFICANT_DOMAINS = ('thermostat',)
|
||||
IGNORE_DOMAINS = ('zone', 'scene',)
|
||||
|
||||
URL_HISTORY_PERIOD = re.compile(
|
||||
r'/api/history/period(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
|
||||
|
||||
|
||||
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()
|
||||
|
||||
query = """
|
||||
@@ -38,17 +37,18 @@ def last_5_states(entity_id):
|
||||
|
||||
|
||||
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,
|
||||
as well as all states from certain domains (for instance
|
||||
thermostat so that we get current temperature in our graphs).
|
||||
|
||||
"""
|
||||
where = """
|
||||
(domain in ({}) or last_changed=last_updated)
|
||||
AND last_updated > ?
|
||||
""".format(",".join(["'%s'" % x for x in SIGNIFICANT_DOMAINS]))
|
||||
(domain IN ({}) OR last_changed=last_updated)
|
||||
AND domain NOT IN ({}) AND last_updated > ?
|
||||
""".format(",".join("'%s'" % x for x in SIGNIFICANT_DOMAINS),
|
||||
",".join("'%s'" % x for x in IGNORE_DOMAINS))
|
||||
|
||||
data = [start_time]
|
||||
|
||||
@@ -63,15 +63,14 @@ def get_significant_states(start_time, end_time=None, entity_id=None):
|
||||
query = ("SELECT * FROM states 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)
|
||||
|
||||
|
||||
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 > ? "
|
||||
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):
|
||||
""" Returns the states at a specific point in time. """
|
||||
"""Return the states at a specific point in time."""
|
||||
if run is None:
|
||||
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):
|
||||
"""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
|
||||
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
|
||||
axis correctly.
|
||||
"""
|
||||
|
||||
result = defaultdict(list)
|
||||
|
||||
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):
|
||||
""" 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)
|
||||
|
||||
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
|
||||
def setup(hass, config):
|
||||
""" Setup history hooks. """
|
||||
"""Setup the history hooks."""
|
||||
hass.http.register_path(
|
||||
'GET',
|
||||
re.compile(
|
||||
@@ -172,14 +170,14 @@ def setup(hass, config):
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=invalid-name
|
||||
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')
|
||||
|
||||
handler.write_json(last_5_states(entity_id))
|
||||
|
||||
|
||||
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')
|
||||
one_day = timedelta(seconds=86400)
|
||||
|
||||
@@ -200,3 +198,13 @@ def _api_history_period(handler, path_match, data):
|
||||
|
||||
handler.write_json(
|
||||
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))
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.http
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
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
|
||||
@@ -52,7 +50,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Sets up the HTTP API and debug interface. """
|
||||
"""Set up the HTTP API and debug interface."""
|
||||
conf = config.get(DOMAIN, {})
|
||||
|
||||
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
|
||||
@@ -76,7 +74,8 @@ def setup(hass, config):
|
||||
hass.bus.listen_once(
|
||||
ha.EVENT_HOMEASSISTANT_START,
|
||||
lambda event:
|
||||
threading.Thread(target=server.start, daemon=True).start())
|
||||
threading.Thread(target=server.start, daemon=True,
|
||||
name='HTTP-server').start())
|
||||
|
||||
hass.http = server
|
||||
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
|
||||
@@ -87,15 +86,16 @@ def setup(hass, config):
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
""" Handle HTTP requests in a threaded fashion. """
|
||||
# pylint: disable=too-few-public-methods
|
||||
"""Handle HTTP requests in a threaded fashion."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, server_address, request_handler_class,
|
||||
hass, api_password, development, ssl_certificate, ssl_key):
|
||||
"""Initialize the server."""
|
||||
super().__init__(server_address, request_handler_class)
|
||||
|
||||
self.server_address = server_address
|
||||
@@ -119,9 +119,9 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
self.socket = context.wrap_socket(self.socket, server_side=True)
|
||||
|
||||
def start(self):
|
||||
""" Starts the HTTP server. """
|
||||
"""Start the HTTP server."""
|
||||
def stop_http(event):
|
||||
""" Stops the HTTP server. """
|
||||
"""Stop the HTTP server."""
|
||||
self.shutdown()
|
||||
|
||||
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
|
||||
@@ -140,19 +140,18 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
self.serve_forever()
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
_LOGGER.info(fmt, *args)
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods,too-many-locals
|
||||
class RequestHandler(SimpleHTTPRequestHandler):
|
||||
"""
|
||||
Handles incoming HTTP requests
|
||||
"""Handle incoming HTTP requests.
|
||||
|
||||
We extend from SimpleHTTPRequestHandler instead of Base so we
|
||||
can use the guess content type methods.
|
||||
@@ -161,13 +160,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
server_version = "HomeAssistant/1.0"
|
||||
|
||||
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
|
||||
self.authenticated = False
|
||||
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
|
||||
|
||||
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:
|
||||
_LOGGER.info(fmt, *arguments)
|
||||
else:
|
||||
@@ -176,7 +175,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
if isinstance(arg, str) else arg for arg in arguments))
|
||||
|
||||
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)
|
||||
|
||||
# Read query input. parse_qs gives a list for each value, we want last
|
||||
@@ -238,9 +237,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
# Did we find a handler for the incoming request?
|
||||
if handle_request_method:
|
||||
# For some calls we need a valid password
|
||||
msg = "API password missing or incorrect."
|
||||
if require_auth and not self.authenticated:
|
||||
self.write_json_message(
|
||||
"API password missing or incorrect.", HTTP_UNAUTHORIZED)
|
||||
self.write_json_message(msg, HTTP_UNAUTHORIZED)
|
||||
_LOGGER.warning(msg)
|
||||
return
|
||||
|
||||
handle_request_method(self, path_match, data)
|
||||
@@ -254,33 +254,36 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
|
||||
def do_HEAD(self): # pylint: disable=invalid-name
|
||||
""" HEAD request handler. """
|
||||
"""HEAD request handler."""
|
||||
self._handle_request('HEAD')
|
||||
|
||||
def do_GET(self): # pylint: disable=invalid-name
|
||||
""" GET request handler. """
|
||||
"""GET request handler."""
|
||||
self._handle_request('GET')
|
||||
|
||||
def do_POST(self): # pylint: disable=invalid-name
|
||||
""" POST request handler. """
|
||||
"""POST request handler."""
|
||||
self._handle_request('POST')
|
||||
|
||||
def do_PUT(self): # pylint: disable=invalid-name
|
||||
""" PUT request handler. """
|
||||
"""PUT request handler."""
|
||||
self._handle_request('PUT')
|
||||
|
||||
def do_DELETE(self): # pylint: disable=invalid-name
|
||||
""" DELETE request handler. """
|
||||
"""DELETE request handler."""
|
||||
self._handle_request('DELETE')
|
||||
|
||||
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)
|
||||
|
||||
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."""
|
||||
json_data = json.dumps(data, indent=4, sort_keys=True,
|
||||
cls=rem.JSONEncoder).encode('UTF-8')
|
||||
self.send_response(status_code)
|
||||
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON)
|
||||
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(json_data)))
|
||||
|
||||
if location:
|
||||
self.send_header('Location', location)
|
||||
@@ -290,23 +293,23 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
|
||||
if data is not None:
|
||||
self.wfile.write(
|
||||
json.dumps(data, indent=4, sort_keys=True,
|
||||
cls=rem.JSONEncoder).encode("UTF-8"))
|
||||
self.wfile.write(json_data)
|
||||
|
||||
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."""
|
||||
msg_data = message.encode('UTF-8')
|
||||
self.send_response(status_code)
|
||||
self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
|
||||
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(msg_data)))
|
||||
|
||||
self.set_session_cookie_header()
|
||||
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(message.encode("UTF-8"))
|
||||
self.wfile.write(msg_data)
|
||||
|
||||
def write_file(self, path, cache_headers=True):
|
||||
""" Returns a file to the user. """
|
||||
"""Return a file to the user."""
|
||||
try:
|
||||
with open(path, 'rb') as inp:
|
||||
self.write_file_pointer(self.guess_type(path), inp,
|
||||
@@ -318,10 +321,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
_LOGGER.exception("Unable to serve %s", path)
|
||||
|
||||
def write_file_pointer(self, content_type, inp, cache_headers=True):
|
||||
"""
|
||||
Helper function to write a file pointer to the user.
|
||||
Does not do error handling.
|
||||
"""
|
||||
"""Helper function to write a file pointer to the user."""
|
||||
do_gzip = 'gzip' in self.headers.get(HTTP_HEADER_ACCEPT_ENCODING, '')
|
||||
|
||||
self.send_response(HTTP_OK)
|
||||
@@ -354,7 +354,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
self.copyfile(inp, self.wfile)
|
||||
|
||||
def set_cache_header(self):
|
||||
""" Add cache headers if not in development """
|
||||
"""Add cache headers if not in development."""
|
||||
if self.server.development:
|
||||
return
|
||||
|
||||
@@ -369,7 +369,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
self.date_time_string(time.time()+cache_time))
|
||||
|
||||
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:
|
||||
return None
|
||||
|
||||
@@ -387,13 +387,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
return session_id
|
||||
|
||||
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
|
||||
|
||||
def get_cookie_session_id(self):
|
||||
"""
|
||||
Extracts the current session id from the
|
||||
cookie or returns None if not set or invalid
|
||||
"""Extract the current session ID from the cookie.
|
||||
|
||||
Return None if not set or invalid.
|
||||
"""
|
||||
if 'Cookie' not in self.headers:
|
||||
return None
|
||||
@@ -417,7 +417,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
return None
|
||||
|
||||
def destroy_session(self):
|
||||
""" Destroys session. """
|
||||
"""Destroy the session."""
|
||||
session_id = self.get_cookie_session_id()
|
||||
|
||||
if session_id is None:
|
||||
@@ -428,27 +428,28 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class SessionStore(object):
|
||||
""" Responsible for storing and retrieving http sessions """
|
||||
"""Responsible for storing and retrieving HTTP sessions."""
|
||||
|
||||
def __init__(self):
|
||||
""" Set up the session store """
|
||||
"""Setup the session store."""
|
||||
self._sessions = {}
|
||||
self._lock = threading.RLock()
|
||||
|
||||
@util.Throttle(SESSION_CLEAR_INTERVAL)
|
||||
def _remove_expired(self):
|
||||
""" Remove any expired sessions. """
|
||||
"""Remove any expired sessions."""
|
||||
now = date_util.utcnow()
|
||||
for key in [key for key, valid_time in self._sessions.items()
|
||||
if valid_time < now]:
|
||||
self._sessions.pop(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:
|
||||
self._remove_expired()
|
||||
|
||||
@@ -456,19 +457,19 @@ class SessionStore(object):
|
||||
self._sessions[key] > date_util.utcnow())
|
||||
|
||||
def extend_validation(self, key):
|
||||
""" Extend a session validation time. """
|
||||
"""Extend a session validation time."""
|
||||
with self._lock:
|
||||
if key not in self._sessions:
|
||||
return
|
||||
self._sessions[key] = session_valid_time()
|
||||
|
||||
def destroy(self, key):
|
||||
""" Destroy a session by key. """
|
||||
"""Destroy a session by key."""
|
||||
with self._lock:
|
||||
self._sessions.pop(key, None)
|
||||
|
||||
def create(self):
|
||||
""" Creates a new session. """
|
||||
"""Create a new session."""
|
||||
with self._lock:
|
||||
session_id = util.get_random_string(20)
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.ifttt
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
This component enable you to trigger Maker IFTTT recipes.
|
||||
Support to trigger Maker IFTTT recipes.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/ifttt/
|
||||
@@ -27,7 +25,7 @@ REQUIREMENTS = ['pyfttt==0.3']
|
||||
|
||||
|
||||
def trigger(hass, event, value1=None, value2=None, value3=None):
|
||||
""" Trigger a Maker IFTTT recipe. """
|
||||
"""Trigger a Maker IFTTT recipe."""
|
||||
data = {
|
||||
ATTR_EVENT: event,
|
||||
ATTR_VALUE1: value1,
|
||||
@@ -38,15 +36,14 @@ def trigger(hass, event, value1=None, value2=None, value3=None):
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup the ifttt service component. """
|
||||
|
||||
"""Setup the IFTTT service component."""
|
||||
if not validate_config(config, {DOMAIN: ['key']}, _LOGGER):
|
||||
return False
|
||||
|
||||
key = config[DOMAIN]['key']
|
||||
|
||||
def trigger_service(call):
|
||||
""" Handle ifttt trigger service calls. """
|
||||
"""Handle IFTTT trigger service calls."""
|
||||
event = call.data.get(ATTR_EVENT)
|
||||
value1 = call.data.get(ATTR_VALUE1)
|
||||
value2 = call.data.get(ATTR_VALUE2)
|
||||
|
||||
@@ -31,8 +31,10 @@ CONF_USERNAME = 'username'
|
||||
CONF_PASSWORD = 'password'
|
||||
CONF_SSL = 'ssl'
|
||||
CONF_VERIFY_SSL = 'verify_ssl'
|
||||
CONF_BLACKLIST = 'blacklist'
|
||||
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
def setup(hass, config):
|
||||
"""Setup the InfluxDB component."""
|
||||
from influxdb import InfluxDBClient, exceptions
|
||||
@@ -52,6 +54,7 @@ def setup(hass, config):
|
||||
ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
|
||||
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool,
|
||||
DEFAULT_VERIFY_SSL)
|
||||
blacklist = conf.get(CONF_BLACKLIST, [])
|
||||
|
||||
try:
|
||||
influx = InfluxDBClient(host=host, port=port, username=username,
|
||||
@@ -67,7 +70,8 @@ def setup(hass, config):
|
||||
def influx_event_listener(event):
|
||||
"""Listen for new messages on the bus and sends them to Influx."""
|
||||
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
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
homeassistant.components.input_boolean
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Component to keep track of user controlled booleans for within automation.
|
||||
|
||||
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):
|
||||
""" Set up input boolean. """
|
||||
"""Set up input boolean."""
|
||||
if not isinstance(config.get(DOMAIN), dict):
|
||||
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
|
||||
return False
|
||||
@@ -68,7 +66,7 @@ def setup(hass, config):
|
||||
return False
|
||||
|
||||
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)
|
||||
|
||||
for input_b in target_inputs:
|
||||
@@ -86,10 +84,10 @@ def setup(hass, config):
|
||||
|
||||
|
||||
class InputBoolean(ToggleEntity):
|
||||
""" Represent a boolean input. """
|
||||
"""Representation of a boolean input."""
|
||||
|
||||
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._name = name
|
||||
self._state = state
|
||||
@@ -97,22 +95,22 @@ class InputBoolean(ToggleEntity):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""If entitiy should be polled."""
|
||||
"""If entity should be polled."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the boolean input."""
|
||||
"""Return name of the boolean input."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Icon to be used for this entity."""
|
||||
"""Returh the icon to be used for this entity."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if entity is on."""
|
||||
"""Return true if entity is on."""
|
||||
return self._state
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user