diff --git a/.coveragerc b/.coveragerc
index 2364cb4d8d1..28b4b926eab 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -37,6 +37,7 @@ omit =
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py
+ homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/thomson.py
@@ -49,8 +50,10 @@ omit =
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/blinksticklight.py
+ homeassistant/components/light/hyperion.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
+ homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py
@@ -70,6 +73,7 @@ omit =
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/command_sensor.py
+ homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/forecast.py
@@ -89,10 +93,12 @@ omit =
homeassistant/components/switch/command_switch.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
+ homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_gpio.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wemo.py
homeassistant/components/thermostat/nest.py
+ homeassistant/components/thermostat/radiotherm.py
[report]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f646766a231..106b914eecb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,8 +20,8 @@ After you finish adding support for your device:
- Update the supported devices in the `README.md` file.
- Add any new dependencies to `requirements_all.txt`. There is no ordering right now, so just add it to the end.
- Update the `.coveragerc` file.
- - Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io).
- - Make sure all your code passes Pylint and flake8 (PEP8 and some more) validation. To generate reports, run `pylint homeassistant > pylint.txt` and `flake8 homeassistant --exclude bower_components,external > flake8.txt`.
+ - Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io). It's OK to add a docstring with configuration details to the file header.
+ - Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `./script/lint`.
- Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant.
- Check for comments and suggestions on your Pull Request and keep an eye on the [Travis output](https://travis-ci.org/balloob/home-assistant/).
diff --git a/Dockerfile b/Dockerfile
index 9554ec552d7..9344ec65245 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,11 +10,10 @@ RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
-# Open Z-Wave disabled because broken
-#RUN apt-get update && \
-# apt-get install -y cython3 libudev-dev && \
-# apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
-# pip3 install cython && \
-# scripts/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
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
diff --git a/README.md b/README.md
index 36777acc517..2fed012402c 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ Examples of devices it can interface it:
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) and any SNMP capable Linksys WAP/WRT
*
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Edimax](http://www.edimax.com/) switches, [Efergy](https://efergy.com) energy monitoring, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
- * [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), [Plex](https://plex.tv/), [Kodi (XBMC)](http://kodi.tv/), and iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api))
+ * [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), [Plex](https://plex.tv/), [Kodi (XBMC)](http://kodi.tv/), iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api)), and Amazon Fire TV (by way of [python-firetv](https://github.com/happyleavesaoc/python-firetv))
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [RFXtrx](http://www.rfxcom.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
* Interaction with [IFTTT](https://ifttt.com/)
* Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org).
diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py
index b85305b6d18..d3289e08e62 100644
--- a/homeassistant/components/alarm_control_panel/__init__.py
+++ b/homeassistant/components/alarm_control_panel/__init__.py
@@ -8,7 +8,7 @@ import os
from homeassistant.components import verisure
from homeassistant.const import (
- ATTR_ENTITY_ID,
+ 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
@@ -29,6 +29,7 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_DISARM: 'alarm_disarm',
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
+ SERVICE_ALARM_TRIGGER: 'alarm_trigger'
}
ATTR_CODE = 'code'
@@ -53,9 +54,9 @@ def setup(hass, config):
target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data:
- return
-
- code = service.data[ATTR_CODE]
+ code = None
+ else:
+ code = service.data[ATTR_CODE]
method = SERVICE_TO_METHOD[service.service]
@@ -72,36 +73,50 @@ def setup(hass, config):
return True
-def alarm_disarm(hass, code, entity_id=None):
+def alarm_disarm(hass, code=None, entity_id=None):
""" Send the alarm the command for disarm. """
- data = {ATTR_CODE: code}
-
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
-def alarm_arm_home(hass, code, entity_id=None):
+def alarm_arm_home(hass, code=None, entity_id=None):
""" Send the alarm the command for arm home. """
- data = {ATTR_CODE: code}
-
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
-def alarm_arm_away(hass, code, entity_id=None):
+def alarm_arm_away(hass, code=None, entity_id=None):
""" Send the alarm the command for arm away. """
- data = {ATTR_CODE: code}
-
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
+def alarm_trigger(hass, code=None, entity_id=None):
+ """ Send the alarm the command for trigger. """
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
+
+
# pylint: disable=no-self-use
class AlarmControlPanel(Entity):
""" ABC for alarm control devices. """
@@ -123,6 +138,10 @@ class AlarmControlPanel(Entity):
""" Send arm away command. """
raise NotImplementedError()
+ def alarm_trigger(self, code=None):
+ """ Send alarm trigger command. """
+ raise NotImplementedError()
+
@property
def state_attributes(self):
""" Return the state attributes. """
diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py
new file mode 100644
index 00000000000..8c98eec50e1
--- /dev/null
+++ b/homeassistant/components/alarm_control_panel/manual.py
@@ -0,0 +1,149 @@
+"""
+homeassistant.components.alarm_control_panel.manual
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Support for manual alarms.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/alarm_control_panel.manual.html
+"""
+import logging
+import datetime
+import homeassistant.components.alarm_control_panel as alarm
+from homeassistant.helpers.event import track_point_in_time
+import homeassistant.util.dt as dt_util
+
+from homeassistant.const import (
+ STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
+ STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = []
+
+DEFAULT_ALARM_NAME = 'HA Alarm'
+DEFAULT_PENDING_TIME = 60
+DEFAULT_TRIGGER_TIME = 120
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """ Sets up the manual alarm platform. """
+
+ add_devices([ManualAlarm(
+ hass,
+ config.get('name', DEFAULT_ALARM_NAME),
+ config.get('code'),
+ config.get('pending_time', DEFAULT_PENDING_TIME),
+ config.get('trigger_time', DEFAULT_TRIGGER_TIME),
+ )])
+
+
+# pylint: disable=too-many-arguments, too-many-instance-attributes
+# pylint: disable=abstract-method
+class ManualAlarm(alarm.AlarmControlPanel):
+ """
+ Represents an alarm status.
+
+ When armed, will be pending for 'pending_time', after that armed.
+ When triggered, will be pending for 'trigger_time'. After that will be
+ triggered for 'trigger_time', after that we return to disarmed.
+ """
+
+ def __init__(self, hass, name, code, pending_time, trigger_time):
+ self._state = STATE_ALARM_DISARMED
+ self._hass = hass
+ self._name = name
+ self._code = str(code) if code else None
+ self._pending_time = datetime.timedelta(seconds=pending_time)
+ self._trigger_time = datetime.timedelta(seconds=trigger_time)
+ self._state_ts = None
+
+ @property
+ def should_poll(self):
+ """ No polling needed. """
+ return False
+
+ @property
+ def name(self):
+ """ Returns the name of the device. """
+ return self._name
+
+ @property
+ def state(self):
+ """ Returns the state of the device. """
+ if self._state in (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) and \
+ self._pending_time and self._state_ts + self._pending_time > \
+ dt_util.utcnow():
+ return STATE_ALARM_PENDING
+
+ if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
+ if self._state_ts + self._pending_time > dt_util.utcnow():
+ return STATE_ALARM_PENDING
+ elif (self._state_ts + self._pending_time +
+ self._trigger_time) < dt_util.utcnow():
+ return STATE_ALARM_DISARMED
+
+ return self._state
+
+ @property
+ def code_format(self):
+ """ One or more characters. """
+ return None if self._code is None else '.+'
+
+ def alarm_disarm(self, code=None):
+ """ Send disarm command. """
+ if not self._validate_code(code, STATE_ALARM_DISARMED):
+ return
+
+ self._state = STATE_ALARM_DISARMED
+ self._state_ts = dt_util.utcnow()
+ self.update_ha_state()
+
+ def alarm_arm_home(self, code=None):
+ """ Send arm home command. """
+ if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
+ return
+
+ self._state = STATE_ALARM_ARMED_HOME
+ self._state_ts = dt_util.utcnow()
+ self.update_ha_state()
+
+ if self._pending_time:
+ track_point_in_time(
+ self._hass, self.update_ha_state,
+ self._state_ts + self._pending_time)
+
+ def alarm_arm_away(self, code=None):
+ """ Send arm away command. """
+ if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
+ return
+
+ self._state = STATE_ALARM_ARMED_AWAY
+ self._state_ts = dt_util.utcnow()
+ self.update_ha_state()
+
+ if self._pending_time:
+ track_point_in_time(
+ self._hass, self.update_ha_state,
+ self._state_ts + self._pending_time)
+
+ def alarm_trigger(self, code=None):
+ """ Send alarm trigger command. No code needed. """
+ self._state = STATE_ALARM_TRIGGERED
+ self._state_ts = dt_util.utcnow()
+ self.update_ha_state()
+
+ if self._trigger_time:
+ track_point_in_time(
+ self._hass, self.update_ha_state,
+ self._state_ts + self._pending_time)
+
+ track_point_in_time(
+ self._hass, self.update_ha_state,
+ self._state_ts + self._pending_time + self._trigger_time)
+
+ def _validate_code(self, code, state):
+ """ Validate given code. """
+ check = self._code is None or code == self._code
+ if not check:
+ _LOGGER.warning('Invalid code given for %s', state)
+ return check
diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py
index c04c8ee6031..e070babd080 100644
--- a/homeassistant/components/alarm_control_panel/mqtt.py
+++ b/homeassistant/components/alarm_control_panel/mqtt.py
@@ -1,68 +1,18 @@
"""
homeassistant.components.alarm_control_panel.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
This platform enables the possibility to control a MQTT alarm.
-In this platform, 'state_topic' and 'command_topic' are required.
-The alarm will only change state after receiving the a new state
-from 'state_topic'. If these messages are published with RETAIN flag,
-the MQTT alarm will receive an instant state update after subscription
-and will start with correct state. Otherwise, the initial state will
-be 'unknown'.
-Configuration:
-
-alarm_control_panel:
- platform: mqtt
- name: "MQTT Alarm"
- state_topic: "home/alarm"
- command_topic: "home/alarm/set"
- qos: 0
- payload_disarm: "DISARM"
- payload_arm_home: "ARM_HOME"
- payload_arm_away: "ARM_AWAY"
- code: "mySecretCode"
-
-Variables:
-
-name
-*Optional
-The name of the alarm. Default is 'MQTT Alarm'.
-
-state_topic
-*Required
-The MQTT topic subscribed to receive state updates.
-
-command_topic
-*Required
-The MQTT topic to publish commands to change the alarm state.
-
-qos
-*Optional
-The maximum QoS level of the state topic. Default is 0.
-This QoS will also be used to publishing messages.
-
-payload_disarm
-*Optional
-The payload do disarm alarm. Default is "DISARM".
-
-payload_arm_home
-*Optional
-The payload to set armed-home mode. Default is "ARM_HOME".
-
-payload_arm_away
-*Optional
-The payload to set armed-away mode. Default is "ARM_AWAY".
-
-code
-*Optional
-If defined, specifies a code to enable or disable the alarm in the frontend.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/alarm_control_panel.mqtt.html
"""
import logging
import homeassistant.components.mqtt as mqtt
import homeassistant.components.alarm_control_panel as alarm
-from homeassistant.const import (STATE_UNKNOWN)
+from homeassistant.const import (
+ STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
+ STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
@@ -99,6 +49,7 @@ 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. """
@@ -113,10 +64,15 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
- self._code = code
+ self._code = str(code) if code else None
def message_received(topic, payload, qos):
""" 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):
+ _LOGGER.warning('Received unexpected payload: %s', payload)
+ return
self._state = payload
self.update_ha_state()
@@ -144,24 +100,28 @@ class MqttAlarm(alarm.AlarmControlPanel):
def alarm_disarm(self, code=None):
""" Send disarm command. """
- if code == str(self._code) or self.code_format is None:
- mqtt.publish(self.hass, self._command_topic,
- self._payload_disarm, self._qos)
- else:
- _LOGGER.warning("Wrong code entered while disarming!")
+ 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. """
- if code == str(self._code) or self.code_format is None:
- mqtt.publish(self.hass, self._command_topic,
- self._payload_arm_home, self._qos)
- else:
- _LOGGER.warning("Wrong code entered while arming home!")
+ 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. """
- if code == str(self._code) or self.code_format is None:
- mqtt.publish(self.hass, self._command_topic,
- self._payload_arm_away, self._qos)
- else:
- _LOGGER.warning("Wrong code entered while arming away!")
+ 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. """
+ check = self._code is None or code == self._code
+ if not check:
+ _LOGGER.warning('Wrong code entered for %s', state)
+ return check
diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py
index c7c24a60c4a..9e0475592bd 100644
--- a/homeassistant/components/alarm_control_panel/verisure.py
+++ b/homeassistant/components/alarm_control_panel/verisure.py
@@ -33,6 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(alarms)
+# pylint: disable=abstract-method
class VerisureAlarm(alarm.AlarmControlPanel):
""" Represents a Verisure alarm status. """
diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py
index e4c794df424..b11525170a4 100644
--- a/homeassistant/components/api.py
+++ b/homeassistant/components/api.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.api
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Provides a Rest API for Home Assistant.
+
+For more details about the RESTful API, please refer to the documentation at
+https://home-assistant.io/developers/api.html
"""
import re
import logging
diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py
index cbb319e2541..12ceafbd44b 100644
--- a/homeassistant/components/arduino.py
+++ b/homeassistant/components/arduino.py
@@ -4,26 +4,8 @@ components.arduino
Arduino component that connects to a directly attached Arduino board which
runs with the Firmata firmware.
-Configuration:
-
-To use the Arduino board you will need to add something like the following
-to your configuration.yaml file.
-
-arduino:
- port: /dev/ttyACM0
-
-Variables:
-
-port
-*Required
-The port where is your board connected to your Home Assistant system.
-If you are using an original Arduino the port will be named ttyACM*. The exact
-number can be determined with 'ls /dev/ttyACM*' or check your 'dmesg'/
-'journalctl -f' output. Keep in mind that Arduino clones are often using a
-different name for the port (e.g. '/dev/ttyUSB*').
-
-A word of caution: The Arduino is not storing states. This means that with
-every initialization the pins are set to off/low.
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/arduino.html
"""
import logging
diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py
index c5b0ee47923..c172b8e0e11 100644
--- a/homeassistant/components/automation/event.py
+++ b/homeassistant/components/automation/event.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.event
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Offers event listening automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/components/automation.html#event-trigger
"""
import logging
diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py
index 3f85792f907..706d97824b4 100644
--- a/homeassistant/components/automation/mqtt.py
+++ b/homeassistant/components/automation/mqtt.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Offers MQTT listening automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/components/automation.html#mqtt-trigger
"""
import logging
diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py
index 7e014213d62..1ddfb91a334 100644
--- a/homeassistant/components/automation/numeric_state.py
+++ b/homeassistant/components/automation/numeric_state.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.numeric_state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Offers numeric state listening automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/components/automation.html#numeric-state-trigger
"""
import logging
diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py
index 8baa0a01d46..52379355d6b 100644
--- a/homeassistant/components/automation/state.py
+++ b/homeassistant/components/automation/state.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Offers state listening automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/components/automation.html#state-trigger
"""
import logging
@@ -28,6 +30,11 @@ def trigger(hass, config, action):
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
+ if isinstance(from_state, bool) or isinstance(to_state, bool):
+ logging.getLogger(__name__).error(
+ 'Config error. Surround to/from values with quotes.')
+ return False
+
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
action()
diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py
index 103df6c9b39..c72474ae4dd 100644
--- a/homeassistant/components/automation/sun.py
+++ b/homeassistant/components/automation/sun.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Offers sun based automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/components/automation.html#sun-trigger
"""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py
index 1d97ccc135d..2f05c6f4390 100644
--- a/homeassistant/components/automation/time.py
+++ b/homeassistant/components/automation/time.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Offers time listening automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/components/automation.html#time-trigger
"""
import logging
diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py
index f62aec8bf2a..28d1c8456f0 100644
--- a/homeassistant/components/automation/zone.py
+++ b/homeassistant/components/automation/zone.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.automation.zone
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Offers zone automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/components/automation.html#zone-trigger
"""
import logging
diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py
index 78fd0f4d2e1..24a42cbf883 100644
--- a/homeassistant/components/camera/foscam.py
+++ b/homeassistant/components/camera/foscam.py
@@ -3,42 +3,6 @@ homeassistant.components.camera.foscam
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component provides basic support for Foscam IP cameras.
-As part of the basic support the following features will be provided:
--MJPEG video streaming
-
-To use this component, add the following to your configuration.yaml file.
-
-camera:
- platform: foscam
- name: Door Camera
- ip: 192.168.0.123
- port: 88
- username: YOUR_USERNAME
- password: YOUR_PASSWORD
-
-Variables:
-
-ip
-*Required
-The IP address of your Foscam device.
-
-username
-*Required
-The username of a visitor or operator of your camera. Oddly admin accounts
-don't seem to have access to take snapshots.
-
-password
-*Required
-The password for accessing your camera.
-
-name
-*Optional
-This parameter allows you to override the name of your camera in homeassistant.
-
-port
-*Optional
-The port that the camera is running on. The default is 88.
-
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.foscam.html
"""
diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py
index 7e4a24ffdfe..74d2d0102d3 100644
--- a/homeassistant/components/camera/generic.py
+++ b/homeassistant/components/camera/generic.py
@@ -3,43 +3,8 @@ homeassistant.components.camera.generic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras.
-This component provides basic support for IP cameras. For the basic support to
-work you camera must support accessing a JPEG snapshot via a URL and you will
-need to specify the "still_image_url" parameter which should be the location of
-the JPEG image.
-
-As part of the basic support the following features will be provided:
-- MJPEG video streaming
-- Saving a snapshot
-- Recording(JPEG frame capture)
-
-To use this component, add the following to your configuration.yaml file.
-
-camera:
- platform: generic
- name: Door Camera
- username: YOUR_USERNAME
- password: YOUR_PASSWORD
- still_image_url: http://YOUR_CAMERA_IP_AND_PORT/image.jpg
-
-Variables:
-
-still_image_url
-*Required
-The URL your camera serves the image on, eg. http://192.168.1.21:2112/
-
-name
-*Optional
-This parameter allows you to override the name of your camera in Home
-Assistant.
-
-username
-*Optional
-The username for accessing your camera.
-
-password
-*Optional
-The password for accessing your camera.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/camera.generic.html
"""
import logging
from requests.auth import HTTPBasicAuth
diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py
index fd2ad60d211..f00a640232f 100644
--- a/homeassistant/components/conversation.py
+++ b/homeassistant/components/conversation.py
@@ -1,9 +1,10 @@
"""
homeassistant.components.conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Provides functionality to have conversations with Home Assistant.
-This is more a proof of concept.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/conversation.html
"""
import logging
import re
diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py
index f22135ec5bc..388a869ae0c 100644
--- a/homeassistant/components/demo.py
+++ b/homeassistant/components/demo.py
@@ -63,6 +63,14 @@ def setup(hass, config):
'still_image_url': 'http://home-assistant.io/demo/webcam.jpg',
}})
+ # Setup alarm_control_panel
+ bootstrap.setup_component(
+ hass, 'alarm_control_panel',
+ {'alarm_control_panel': {
+ 'platform': 'manual',
+ 'name': 'Test Alarm',
+ }})
+
# Setup scripts
bootstrap.setup_component(
hass, 'script',
diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py
index f926b182983..a2f94f34c3a 100644
--- a/homeassistant/components/device_tracker/actiontec.py
+++ b/homeassistant/components/device_tracker/actiontec.py
@@ -4,41 +4,8 @@ homeassistant.components.device_tracker.actiontec
Device tracker platform that supports scanning an Actiontec MI424WR
(Verizon FIOS) router for device presence.
-This device tracker needs telnet to be enabled on the router.
-
-Configuration:
-
-To use the Actiontec tracker you will need to add something like the
-following to your configuration.yaml file. If you experience disconnects
-you can modify the home_interval variable.
-
-device_tracker:
- platform: actiontec
- host: YOUR_ROUTER_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
- # optional:
- home_interval: 10
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
-
-home_interval
-*Optional
-If the home_interval is set then the component will not let a device
-be AWAY if it has been HOME in the last home_interval minutes. This is
-in addition to the 3 minute wait built into the device_tracker component.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.actiontec.html
"""
import logging
from datetime import timedelta
@@ -56,7 +23,7 @@ from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
-# interval in minutes to exclude devices from a scan while they are home
+# Interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py
index 68ff8390216..d46264fa264 100644
--- a/homeassistant/components/device_tracker/aruba.py
+++ b/homeassistant/components/device_tracker/aruba.py
@@ -4,33 +4,8 @@ homeassistant.components.device_tracker.aruba
Device tracker platform that supports scanning a Aruba Access Point for device
presence.
-This device tracker needs telnet to be enabled on the router.
-
-Configuration:
-
-To use the Aruba tracker you will need to add something like the following
-to your configuration.yaml file. You also need to enable Telnet in the
-configuration page of your router.
-
-device_tracker:
- platform: aruba
- host: YOUR_ACCESS_POINT_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.aruba.html
"""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py
index 1e3ac20b6f2..5284d45835b 100644
--- a/homeassistant/components/device_tracker/asuswrt.py
+++ b/homeassistant/components/device_tracker/asuswrt.py
@@ -161,9 +161,10 @@ class AsusWrtDeviceScanner(object):
# 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 = ''
+ if match:
+ host = match.group('host')
+ if host == '*':
+ host = ''
devices[match.group('ip')] = {
'host': host,
@@ -174,6 +175,6 @@ class AsusWrtDeviceScanner(object):
for neighbor in neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
- if match.group('ip') in devices:
+ if match and match.group('ip') in devices:
devices[match.group('ip')]['status'] = match.group('status')
return devices
diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py
index 947876c85b5..d8734a55a17 100644
--- a/homeassistant/components/device_tracker/ddwrt.py
+++ b/homeassistant/components/device_tracker/ddwrt.py
@@ -4,30 +4,8 @@ homeassistant.components.device_tracker.ddwrt
Device tracker platform that supports scanning a DD-WRT router for device
presence.
-Configuration:
-
-To use the DD-WRT tracker you will need to add something like the following
-to your configuration.yaml file.
-
-device_tracker:
- platform: ddwrt
- host: YOUR_ROUTER_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.ddwrt.html
"""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/device_tracker/geofancy.py b/homeassistant/components/device_tracker/geofancy.py
new file mode 100644
index 00000000000..91d3978326b
--- /dev/null
+++ b/homeassistant/components/device_tracker/geofancy.py
@@ -0,0 +1,71 @@
+"""
+homeassistant.components.device_tracker.geofancy
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Geofancy platform for the device tracker.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.geofancy.html
+"""
+
+from homeassistant.const import (
+ HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR)
+
+DEPENDENCIES = ['http']
+
+_SEE = 0
+
+URL_API_GEOFANCY_ENDPOINT = "/api/geofancy"
+
+
+def setup_scanner(hass, config, see):
+ """ Set up an endpoint for the Geofancy app. """
+
+ # Use a global variable to keep setup_scanner compact when using a callback
+ global _SEE
+ _SEE = see
+
+ # POST would be semantically better, but that currently does not work
+ # since Geofancy 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_GEOFANCY_ENDPOINT, _handle_get_api_geofancy)
+
+ return True
+
+
+def _handle_get_api_geofancy(handler, path_match, data):
+ """ Geofancy message received. """
+
+ if not isinstance(data, dict):
+ handler.write_json_message(
+ "Error while parsing Geofancy message.",
+ HTTP_INTERNAL_SERVER_ERROR)
+ return
+ if 'latitude' not in data or 'longitude' not in data:
+ handler.write_json_message(
+ "Location not specified.",
+ HTTP_UNPROCESSABLE_ENTITY)
+ return
+ if 'device' not in data or 'id' not in data:
+ handler.write_json_message(
+ "Device id or location id not specified.",
+ HTTP_UNPROCESSABLE_ENTITY)
+ return
+
+ try:
+ gps_coords = (float(data['latitude']), float(data['longitude']))
+ except ValueError:
+ # If invalid latitude / longitude format
+ handler.write_json_message(
+ "Invalid latitude / longitude format.",
+ HTTP_UNPROCESSABLE_ENTITY)
+ return
+
+ # entity id's in Home Assistant must be alphanumerical
+ device_uuid = data['device']
+ device_entity_id = device_uuid.replace('-', '')
+
+ _SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id'])
+
+ handler.write_json_message("Geofancy message processed")
diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py
index 4cbc6a2d492..2ce032f90fd 100644
--- a/homeassistant/components/device_tracker/luci.py
+++ b/homeassistant/components/device_tracker/luci.py
@@ -4,33 +4,8 @@ homeassistant.components.device_tracker.luci
Device tracker platform that supports scanning a OpenWRT router for device
presence.
-It's required that the luci RPC package is installed on the OpenWRT router:
-# opkg install luci-mod-rpc
-
-Configuration:
-
-To use the Luci tracker you will need to add something like the following
-to your configuration.yaml file.
-
-device_tracker:
- platform: luci
- host: YOUR_ROUTER_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.luci.html
"""
import logging
import json
diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py
index 34cee8f6733..f78cb3420f5 100644
--- a/homeassistant/components/device_tracker/mqtt.py
+++ b/homeassistant/components/device_tracker/mqtt.py
@@ -1,15 +1,10 @@
"""
homeassistant.components.device_tracker.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
MQTT platform for the device tracker.
-device_tracker:
- platform: mqtt
- qos: 1
- devices:
- paulus_oneplus: /location/paulus
- annetherese_n4: /location/annetherese
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.mqtt.html
"""
import logging
from homeassistant import util
diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py
index 46c515dcb1f..2d138cf5c70 100644
--- a/homeassistant/components/device_tracker/netgear.py
+++ b/homeassistant/components/device_tracker/netgear.py
@@ -4,30 +4,8 @@ homeassistant.components.device_tracker.netgear
Device tracker platform that supports scanning a Netgear router for device
presence.
-Configuration:
-
-To use the Netgear tracker you will need to add something like the following
-to your configuration.yaml file.
-
-device_tracker:
- platform: netgear
- host: YOUR_ROUTER_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.netgear.html
"""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py
index 6f993f0fc7e..fe6b814b96f 100644
--- a/homeassistant/components/device_tracker/nmap_tracker.py
+++ b/homeassistant/components/device_tracker/nmap_tracker.py
@@ -3,26 +3,8 @@ homeassistant.components.device_tracker.nmap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a network with nmap.
-Configuration:
-
-To use the nmap tracker you will need to add something like the following
-to your configuration.yaml file.
-
-device_tracker:
- platform: nmap_tracker
- hosts: 192.168.1.1/24
-
-Variables:
-
-hosts
-*Required
-The IP addresses to scan in the network-prefix notation (192.168.1.1/24) or
-the range notation (192.168.1.1-255).
-
-home_interval
-*Optional
-Number of minutes it will not scan devices that it found in previous results.
-This is to save battery.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.nmap_scanner.html
"""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py
index 505fd6b7ad2..78fd42f1566 100644
--- a/homeassistant/components/device_tracker/owntracks.py
+++ b/homeassistant/components/device_tracker/owntracks.py
@@ -1,11 +1,10 @@
"""
homeassistant.components.device_tracker.owntracks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
OwnTracks platform for the device tracker.
-device_tracker:
- platform: owntracks
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.owntracks.html
"""
import json
import logging
diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py
index 408daa94d81..c6679e6c320 100644
--- a/homeassistant/components/device_tracker/thomson.py
+++ b/homeassistant/components/device_tracker/thomson.py
@@ -4,32 +4,8 @@ homeassistant.components.device_tracker.thomson
Device tracker platform that supports scanning a THOMSON router for device
presence.
-This device tracker needs telnet to be enabled on the router.
-
-Configuration:
-
-To use the THOMSON tracker you will need to add something like the following
-to your configuration.yaml file.
-
-device_tracker:
- platform: thomson
- host: YOUR_ROUTER_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.thomson.html
"""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py
index a23b7b80ff0..df0c9c8d93d 100644
--- a/homeassistant/components/device_tracker/tomato.py
+++ b/homeassistant/components/device_tracker/tomato.py
@@ -4,36 +4,8 @@ homeassistant.components.device_tracker.tomato
Device tracker platform that supports scanning a Tomato router for device
presence.
-Configuration:
-
-To use the Tomato tracker you will need to add something like the following
-to your configuration.yaml file.
-
-device_tracker:
- platform: tomato
- host: YOUR_ROUTER_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
- http_id: ABCDEFG
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
-
-http_id
-*Required
-The value can be obtained by logging in to the Tomato admin interface and
-search for http_id in the page source code.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.tomato.html
"""
import logging
import json
diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py
index 6b12000cf45..3769229f101 100755
--- a/homeassistant/components/device_tracker/tplink.py
+++ b/homeassistant/components/device_tracker/tplink.py
@@ -4,30 +4,8 @@ homeassistant.components.device_tracker.tplink
Device tracker platform that supports scanning a TP-Link router for device
presence.
-Configuration:
-
-To use the TP-Link tracker you will need to add something like the following
-to your configuration.yaml file.
-
-device_tracker:
- platform: tplink
- host: YOUR_ROUTER_IP
- username: YOUR_ADMIN_USERNAME
- password: YOUR_ADMIN_PASSWORD
-
-Variables:
-
-host
-*Required
-The IP address of your router, e.g. 192.168.1.1.
-
-username
-*Required
-The username of an user with administrative privileges, usually 'admin'.
-
-password
-*Required
-The password for your given admin account.
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.tplink.html
"""
import base64
import logging
@@ -54,10 +32,13 @@ def get_scanner(hass, config):
_LOGGER):
return None
- scanner = Tplink2DeviceScanner(config[DOMAIN])
+ scanner = Tplink3DeviceScanner(config[DOMAIN])
if not scanner.success_init:
- scanner = TplinkDeviceScanner(config[DOMAIN])
+ scanner = Tplink2DeviceScanner(config[DOMAIN])
+
+ if not scanner.success_init:
+ scanner = TplinkDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@@ -156,7 +137,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
with self.lock:
_LOGGER.info("Loading wireless clients...")
- url = 'http://{}/data/map_access_wireless_client_grid.json'\
+ url = 'http://{}/data/map_access_wireless_client_grid.json' \
.format(self.host)
referer = 'http://{}'.format(self.host)
@@ -166,7 +147,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
b64_encoded_username_password = base64.b64encode(
username_password.encode('ascii')
).decode('ascii')
- cookie = 'Authorization=Basic {}'\
+ cookie = 'Authorization=Basic {}' \
.format(b64_encoded_username_password)
response = requests.post(url, headers={'referer': referer,
@@ -183,7 +164,119 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
self.last_results = {
device['mac_addr'].replace('-', ':'): device['name']
for device in result
- }
+ }
+ return True
+
+ return False
+
+
+class Tplink3DeviceScanner(TplinkDeviceScanner):
+ """
+ This class queries the Archer C9 router running version 150811 or higher
+ of TP-Link firmware for connected devices.
+ """
+
+ def __init__(self, config):
+ 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.
+ """
+
+ 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.
+ 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.
+ """
+
+ _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
+
+ response = requests.post(url,
+ params={'operation': 'login',
+ 'username': self.username,
+ 'password': self.password},
+ headers={'referer': referer})
+
+ try:
+ self.stok = response.json().get('data').get('stok')
+ _LOGGER.info(self.stok)
+ regex_result = re.search('sysauth=(.*);',
+ response.headers['set-cookie'])
+ self.sysauth = regex_result.group(1)
+ _LOGGER.info(self.sysauth)
+ return True
+ except ValueError:
+ _LOGGER.error("Couldn't fetch auth tokens!")
+ return False
+
+ @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.
+ """
+
+ with self.lock:
+ if (self.stok == '') or (self.sysauth == ''):
+ self._get_auth_tokens()
+
+ _LOGGER.info("Loading wireless clients...")
+
+ url = 'http://{}/cgi-bin/luci/;stok={}/admin/wireless?form=statistics' \
+ .format(self.host, self.stok)
+ referer = 'http://{}/webpages/index.html'.format(self.host)
+
+ response = requests.post(url,
+ params={'operation': 'load'},
+ headers={'referer': referer},
+ cookies={'sysauth': self.sysauth})
+
+ try:
+ json_response = response.json()
+
+ if json_response.get('success'):
+ result = response.json().get('data')
+ else:
+ if json_response.get('errorcode') == 'timeout':
+ _LOGGER.info("Token timed out. "
+ "Relogging on next scan.")
+ self.stok = ''
+ self.sysauth = ''
+ return False
+ else:
+ _LOGGER.error("An unknown error happened "
+ "while fetching data.")
+ return False
+ except ValueError:
+ _LOGGER.error("Router didn't respond with JSON. "
+ "Check if credentials are correct.")
+ return False
+
+ if result:
+ self.last_results = {
+ device['mac'].replace('-', ':'): device['mac']
+ for device in result
+ }
return True
return False
diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py
new file mode 100644
index 00000000000..195ed33e77b
--- /dev/null
+++ b/homeassistant/components/device_tracker/ubus.py
@@ -0,0 +1,173 @@
+"""
+homeassistant.components.device_tracker.ubus
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Device tracker platform that supports scanning a OpenWRT router for device
+presence.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/device_tracker.ubus.html
+"""
+import logging
+import json
+from datetime import timedelta
+import re
+import threading
+import requests
+
+from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
+from homeassistant.helpers import validate_config
+from homeassistant.util import Throttle
+from homeassistant.components.device_tracker import DOMAIN
+
+# Return cached results if last scan was less then this time ago
+MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def get_scanner(hass, config):
+ """ Validates config and returns a Luci scanner. """
+ if not validate_config(config,
+ {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
+ _LOGGER):
+ return None
+
+ scanner = UbusDeviceScanner(config[DOMAIN])
+
+ return scanner if scanner.success_init else None
+
+
+# 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
+
+ """
+
+ def __init__(self, config):
+ host = config[CONF_HOST]
+ username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
+
+ self.parse_api_pattern = re.compile(r"(?P\w*) = (?P.*);")
+ self.lock = threading.Lock()
+ self.last_results = {}
+ self.url = 'http://{}/ubus'.format(host)
+
+ self.session_id = _get_session_id(self.url, username, password)
+ self.hostapd = []
+ self.leasefile = None
+ self.mac2name = None
+ 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.
+ """
+
+ 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. """
+
+ with self.lock:
+ if self.leasefile is None:
+ result = _req_json_rpc(self.url, self.session_id,
+ 'call', 'uci', 'get',
+ config="dhcp", type="dnsmasq")
+ if result:
+ values = result["values"].values()
+ self.leasefile = next(iter(values))["leasefile"]
+ else:
+ return
+
+ if self.mac2name is None:
+ result = _req_json_rpc(self.url, self.session_id,
+ 'call', 'file', 'read',
+ path=self.leasefile)
+ if result:
+ self.mac2name = dict()
+ for line in result["data"].splitlines():
+ hosts = line.split(" ")
+ self.mac2name[hosts[1].upper()] = hosts[3]
+ else:
+ # Error, handled in the _req_json_rpc
+ return
+
+ return self.mac2name.get(device.upper(), None)
+
+ @Throttle(MIN_TIME_BETWEEN_SCANS)
+ def _update_info(self):
+ """
+ Ensures the information from the Luci router is up to date.
+ Returns boolean if scanning successful.
+ """
+ if not self.success_init:
+ return False
+
+ with self.lock:
+ _LOGGER.info("Checking ARP")
+
+ if not self.hostapd:
+ hostapd = _req_json_rpc(self.url, self.session_id,
+ 'list', 'hostapd.*', '')
+ self.hostapd.extend(hostapd.keys())
+
+ self.last_results = []
+ results = 0
+ for hostapd in self.hostapd:
+ result = _req_json_rpc(self.url, self.session_id,
+ 'call', hostapd, 'get_clients')
+
+ if result:
+ results = results + 1
+ self.last_results.extend(result['clients'].keys())
+
+ return bool(results)
+
+
+def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
+ """ Perform one JSON RPC operation. """
+
+ data = json.dumps({"jsonrpc": "2.0",
+ "id": 1,
+ "method": rpcmethod,
+ "params": [session_id,
+ subsystem,
+ method,
+ params]})
+
+ try:
+ res = requests.post(url, data=data, timeout=5)
+
+ except requests.exceptions.Timeout:
+ return
+
+ if res.status_code == 200:
+ response = res.json()
+
+ if rpcmethod == "call":
+ return response["result"][1]
+ else:
+ return response["result"]
+
+
+def _get_session_id(url, username, password):
+ """ Get authentication token for the given host+username+password. """
+ res = _req_json_rpc(url, "00000000000000000000000000000000", 'call',
+ 'session', 'login', username=username,
+ password=password)
+ return res["ubus_rpc_session"]
diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py
index 089db3fb324..f61d792bc60 100644
--- a/homeassistant/components/discovery.py
+++ b/homeassistant/components/discovery.py
@@ -19,7 +19,7 @@ from homeassistant.const import (
DOMAIN = "discovery"
DEPENDENCIES = []
-REQUIREMENTS = ['netdisco==0.4.2']
+REQUIREMENTS = ['netdisco==0.5.1']
SCAN_INTERVAL = 300 # seconds
@@ -28,6 +28,7 @@ SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos'
+SERVICE_PLEX = 'plex_mediaserver'
SERVICE_HANDLERS = {
SERVICE_WEMO: "switch",
@@ -35,6 +36,7 @@ SERVICE_HANDLERS = {
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
+ SERVICE_PLEX: 'media_player',
}
@@ -88,6 +90,7 @@ def setup(hass, config):
ATTR_DISCOVERED: info
})
+ # pylint: disable=unused-argument
def start_discovery(event):
""" Start discovering. """
netdisco = DiscoveryService(SCAN_INTERVAL)
diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader.py
index 6978dbd7fa9..f145fadfb71 100644
--- a/homeassistant/components/downloader.py
+++ b/homeassistant/components/downloader.py
@@ -1,8 +1,10 @@
"""
homeassistant.components.downloader
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Provides functionality to download files.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/downloader.html
"""
import os
import logging
@@ -42,6 +44,10 @@ def setup(hass, config):
download_path = config[DOMAIN][CONF_DOWNLOAD_DIR]
+ # If path is relative, we assume relative to HASS config dir
+ if not os.path.isabs(download_path):
+ download_path = hass.config.path(download_path)
+
if not os.path.isdir(download_path):
logger.error(
diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py
index abf0c498b1a..1c753d1638e 100644
--- a/homeassistant/components/frontend/version.py
+++ b/homeassistant/components/frontend/version.py
@@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
-VERSION = "c4722afa376379bc4457d54bb9a38cee"
+VERSION = "beb922c55bb26ea576581b453f6d7c04"
diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html
index 73fdb905114..7343bd3afd0 100644
--- a/homeassistant/components/frontend/www_static/frontend.html
+++ b/homeassistant/components/frontend/www_static/frontend.html
@@ -1,6 +1,584 @@
-