diff --git a/.coveragerc b/.coveragerc index 32458b4c606..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,6 +50,7 @@ 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 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/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/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/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 0b3cc1025cc..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.5'] +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 98deab3f447..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 = "90c41bfbaa56f9a1c88db27a54f7d36b" +VERSION = "beb922c55bb26ea576581b453f6d7c04" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 18bb5c557f9..7343bd3afd0 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,6 +1,584 @@ - [[title]][[header]][[title]][[header]][[stateObj.stateDisplay]]Currently: [[stateObj.attributes.current_temperature]] [[stateObj.attributes.unit_of_measurement]][[computeTargetTemperature(stateObj)]]Currently: [[stateObj.attributes.current_temperature]] [[stateObj.attributes.unit_of_measurement]][[locationName]][[locationName]][[_charCounterStr]][[_charCounterStr]][[label]][[errorMessage]][[label]][[errorMessage]] [[label]][[errorMessage]] [[label]][[errorMessage]][[_text]][[_text]][[stateObj.attributes.description]][[stateObj.attributes.errors]][[submitCaption]]Configuring[[stateObj.attributes.description]][[stateObj.attributes.errors]]{{item.name}}[[submitCaption]]Configuring \ No newline at end of file + }
[[stateObj.attributes.description]]
[[stateObj.attributes.errors]]
[[submitCaption]]Configuring